The other way to define beans — annotate your own classes and let Spring find and wire them.
@Bean methods). This is the flip side: put a
stereotype annotation on your class, and component scanning turns it into a bean automatically.
Then @Autowired wires the dependencies. Expect several questions on stereotypes, how
@Autowired resolves, and @PostConstruct/@PreDestroy.
A stereotype is a specialised @Component. All of them are meta-annotated
with @Component, so component scanning detects them all identically — the different names carry
intent (and sometimes extra behaviour):
| Annotation | Marks | Extra behaviour |
|---|---|---|
@Component | Any Spring-managed component | — |
@Service | A service / business-logic bean | None (semantic only) |
@Repository | A persistence / DAO bean | Exception translation — vendor SQLExceptions become Spring DataAccessExceptions |
@Controller | A web MVC controller | Handler mapping (Lesson 11) |
@RestController | A REST controller | = @Controller + @ResponseBody |
@Repository is the only stereotype that adds real behaviour — persistence exception
translation. (2) @Configuration is also a @Component, which is why
component scanning finds your config classes.
@ComponentScan@Configuration
@ComponentScan("com.example.app") // scan this package + sub-packages
public class AppConfig { }
// elsewhere — this class becomes a bean automatically
@Service
public class TransferService {
private final AccountRepository repo;
// single constructor → @Autowired is optional (Spring 4.3+)
public TransferService(AccountRepository repo) { this.repo = repo; }
}
With no argument, @ComponentScan scans the package of the annotated class and downward — which is
why @SpringBootApplication (it includes @ComponentScan) sits in a top-level package.
@Autowired resolves a dependencyThis is the mental model to memorise — resolution happens in order:
@Qualifier → then @Primary → then by bean name matching the field/parameter name.NoUniqueBeanDefinitionException. If none and it's required → NoSuchBeanDefinitionException.@Qualifier/@Primary → the context fails to start
with NoUniqueBeanDefinitionException. The fixes: mark one @Primary, or select with
@Qualifier("beanName") at the injection point, or name the parameter to match a bean id.
| Tool | Goes where | Effect |
|---|---|---|
@Primary | On a bean definition | Makes it the default winner when multiple match |
@Qualifier("id") | At the injection point | Explicitly picks that bean — beats @Primary |
| Parameter/field name | Injection point | Falls back to matching a bean whose id equals the name |
@Autowired can gofinal, hides dependencies, hard to unit-test).Make a dependency optional with @Autowired(required = false), Optional<T>, or
@Nullable. Inject all beans of a type with a List<T> or Map<String,T>.
@PostConstruct and @PreDestroyThese JSR-250 annotations (javax.annotation.*) hook the bean's lifecycle without coupling your
class to a Spring interface:
@Service
public class Cache {
@PostConstruct // runs AFTER dependencies are injected, before use
void warmUp() { ... }
@PreDestroy // runs before the bean is destroyed
void flush() { ... }
}
@PostConstruct → InitializingBean.afterPropertiesSet()
→ custom init-method.@PreDestroy → DisposableBean.destroy() → custom destroy-method.
@Bean methods in @Configuration (Lesson 2).Read "Using @Autowired", "Fine-tuning… with @Primary/@Qualifier", and "@PostConstruct and @PreDestroy". Mirrors this lesson exactly.
The @Autowired resolution-order question and the multiple-matching-beans ambiguity trap are the high-value ones. Options shuffle on every load.
@Value, choosing which bean to inject,
stereotypes, @Autowired placement, what component scanning detects). Re-run the
Container Drill — the stereotype, component-scan and
"which bean to inject" questions should now feel easy.
@Autowired resolution order or the @PostConstruct timing is fuzzy, ask me to
trace a concrete example. Say "continue" for Lesson 4 — Bean Scopes.