Bean Management
@Component, @Service, @Repository, @Configuration, @Bean, @Primary, @Qualifier
Bean Management
1. Definition
Spring Framework bean management refers to the registration, configuration, and lifecycle control of container-managed objects. Spring offers two approaches: automatic discovery with stereotype annotations (@Component, @Service, @Repository) and explicit declaration with @Bean methods in @Configuration classes. The combination of both approaches forms the configuration foundation of modern Spring applications.
Beyond bean registration, Spring provides mechanisms for disambiguation (@Primary, @Qualifier), bean definition modification (BeanFactoryPostProcessor), and bean instance customization (BeanPostProcessor).
2. Core Concepts
Stereotype Annotations
Spring defines four main stereotype annotations, each a specialization of @Component:
| Annotation | Purpose | Extra behavior |
|---|---|---|
@Component |
General bean | Base component marker |
@Service |
Business logic layer | Semantic marker (no extra logic) |
@Repository |
Data access layer | Exception translation (SQL → DataAccessException) |
@Controller |
Web layer | HTTP request mapping (with @RequestMapping family) |
@Serviceand@Componentare technically identical — the difference is semantic: it signals to developers and tools what role the class plays.
@Configuration and @Bean
A @Configuration class is a configuration source where @Bean methods create explicit bean definitions:
@Configuration
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
When @Bean vs @Component?
@Component: for your own classes where you can add the annotation@Bean: for third-party classes you cannot modify, or when complex initialization is needed
@Primary and @Qualifier
When an interface has multiple implementations, Spring cannot automatically resolve:
public interface NotificationSender { void send(String msg); }
@Service
@Primary
public class EmailSender implements NotificationSender { ... }
@Service
public class SmsSender implements NotificationSender { ... }
@Primary— default implementation when no explicit selection is made@Qualifier("smsSender")— named selection at injection point
@Service
public class AlertService {
// Gets @Primary (EmailSender)
private final NotificationSender defaultSender;
// Gets SmsSender via explicit @Qualifier
private final NotificationSender smsSender;
public AlertService(
NotificationSender defaultSender,
@Qualifier("smsSender") NotificationSender smsSender) {
this.defaultSender = defaultSender;
this.smsSender = smsSender;
}
}
Custom Qualifier Annotation
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Sms {}
@Service @Sms
public class SmsSender implements NotificationSender { ... }
// Usage
public AlertService(@Sms NotificationSender sender) { ... }
3. Practical Usage
Component Scanning
@Configuration
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Deprecated.class
)
)
public class AppConfig {}
Conditional Bean Registration
@Configuration
public class CacheConfig {
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("items");
}
}
Bean Collection Injection
@Service
public class NotificationRouter {
private final List<NotificationSender> senders; // all implementations
public NotificationRouter(List<NotificationSender> senders) {
this.senders = senders;
}
public void notifyAll(String msg) {
senders.forEach(s -> s.send(msg));
}
}
@Order and @Priority
@Service @Order(1)
public class EmailSender implements NotificationSender { ... }
@Service @Order(2)
public class SmsSender implements NotificationSender { ... }
// List<NotificationSender> starts with EmailSender
4. Code Examples
Full Layered Application
// Repository layer
@Repository
public class JpaUserRepository implements UserRepository {
private final EntityManager em;
public JpaUserRepository(EntityManager em) {
this.em = em;
}
@Override
public User findById(Long id) {
return em.find(User.class, id);
}
}
// Service layer
@Service
public class UserService {
private final UserRepository userRepo;
public UserService(UserRepository userRepo) {
this.userRepo = userRepo;
}
public User getUser(Long id) {
return userRepo.findById(id);
}
}
@Bean initMethod / destroyMethod
@Configuration
public class DataSourceConfig {
@Bean(initMethod = "start", destroyMethod = "close")
public HikariDataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:test");
return ds;
}
}
BeanPostProcessor Example
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof Auditable) {
System.out.println("Initializing auditable bean: " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean; // AOP proxies are created here
}
}
BeanFactoryPostProcessor Example
@Component
public class CustomPropertyPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
BeanDefinition bd = factory.getBeanDefinition("dataSource");
bd.getPropertyValues().addPropertyValue("maxPoolSize", "20");
}
}
5. Trade-offs
| Aspect | @Component (scan) | @Bean (explicit) |
|---|---|---|
| Simplicity | ✅ Less code | ❌ More boilerplate |
| Transparency | ❌ Scattered | ✅ Visible in one place |
| Third-party classes | ❌ Cannot modify | ✅ Anything registrable |
| IDE navigation | ✅ Annotation → class | ✅ Method → return type |
| Conditional reg. | @Conditional annotations | @ConditionalOnProperty etc. |
| Aspect | @Primary | @Qualifier |
|---|---|---|
| Default | ✅ Global default | ❌ Must specify at every injection |
| Precision | ❌ Only one allowed | ✅ Any can be selected |
| Maintainability | ✅ Simple | ❌ String-based = fragile |
| Custom qualifier | — | ✅ Type-safe annotation |
BeanPostProcessor vs BeanFactoryPostProcessor
| Aspect | BeanPostProcessor | BeanFactoryPostProcessor |
|---|---|---|
| When does it run? | After bean instantiation | After bean definitions loaded |
| What does it modify? | Bean instances | Bean definitions (metadata) |
| Typical purpose | AOP proxy, validation, logging | Property resolution, bean def modification |
6. Common Mistakes
❌ DAO Class Without @Repository
// BAD — no exception translation
@Component
public class UserDao { ... }
// GOOD — DataAccessException mapping is automatic
@Repository
public class UserDao { ... }
❌ @Bean Method Not in @Configuration Class
// BAD — in @Component, @Bean methods run in "lite mode"
// Inter-bean references do NOT maintain singleton semantics!
@Component
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(commonDep()); // NEW instance on every call!
}
@Bean
public CommonDep commonDep() { return new CommonDep(); }
}
// GOOD — @Configuration CGLIB proxy ensures singleton
@Configuration
public class AppConfig { ... }
❌ @Qualifier String Typo
// BAD — runtime error if name doesn't match
@Qualifier("smsSennder") // Typo!
NotificationSender sender;
// GOOD — custom qualifier annotation = compile-time safety
@Sms NotificationSender sender;
❌ @Autowired Dependency in BeanPostProcessor
// BAD — BPP is instantiated early, dependencies may not be ready
@Component
public class MyBPP implements BeanPostProcessor {
@Autowired
private SomeService service; // May be null or not proxied!
}
❌ Multiple @Primary on Same Interface
// BAD — NoUniqueBeanDefinitionException
@Service @Primary
public class EmailSender implements NotificationSender { ... }
@Service @Primary
public class PushSender implements NotificationSender { ... }
7. Deep Dive
Stereotype Annotation Inheritance
@Service, @Repository, @Controller all contain @Component as a meta-annotation. Spring component scanning looks for the presence of @Component (either directly or via meta-annotations). This is why custom stereotype annotations work:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface UseCase {}
@UseCase
public class PlaceOrderUseCase { ... } // registered as a bean
BeanPostProcessor Internals
BeanPostProcessor is part of the bean pipeline:
- Bean instantiation (constructor)
- Dependency injection (setter/field)
postProcessBeforeInitialization()— all BPPs@PostConstruct/InitializingBean.afterPropertiesSet()postProcessAfterInitialization()— all BPPs (AOP proxy created here)- Bean ready for use
Important: If a BPP wraps the bean (e.g., returns a proxy), the original bean reference is lost. This is exactly how Spring AOP works.
BeanFactoryPostProcessor and Property Placeholder
PropertySourcesPlaceholderConfigurer (responsible for resolving @Value("${...}")) is itself a BeanFactoryPostProcessor. This explains why you cannot use @Value in a BeanFactoryPostProcessor — it hasn't run yet when the BFPP is instantiated.
Bean Overriding
Since Spring Boot 2.1+, bean overriding is disabled by default (spring.main.allow-bean-definition-overriding=false). If two @Bean definitions produce the same name, BeanDefinitionOverrideException is thrown. This prevents accidental override.
@Lazy
@Service
@Lazy
public class HeavyService { ... }
// Only instantiated when first injected/looked up
@Lazy is useful for startup time optimization but can hide configuration errors (not fail-fast).
Related topic: The Spring Design Patterns chapter (→ Chapter 16) discusses in detail how Bean Management patterns relate to GoF patterns (Factory, Singleton, Template Method).
8. Interview Questions
What is the difference between @Component and @Bean?
@Componentis class-level, registered by component scan.@Beanis method-level in@Configurationclass, works for third-party classes too.What is the difference between @Service and @Repository? Both are
@Componentspecializations.@Repositoryadds exception translation (SQL exception →DataAccessException).@Serviceis purely a semantic marker.How do you resolve multiple implementations of the same interface?
@Primary(global default),@Qualifier("name")(explicit name), custom qualifier annotation (type-safe), orList<T>injection (all of them).What is BeanPostProcessor? A hook into the bean lifecycle.
postProcessBeforeInitializationandpostProcessAfterInitialization. AOP proxies are created inpostProcessAfterInitialization.What is BeanFactoryPostProcessor? Modifies bean definitions before instantiation. E.g.,
PropertySourcesPlaceholderConfigurerfor@Valueresolution.Why is @Configuration important (vs @Component) for @Bean methods?
@Configurationgets a CGLIB proxy → inter-bean references maintain singleton semantics. In@Component,@Beanmethods run in "lite mode" — no proxy, every call creates a new instance.What is bean overriding and how does Spring Boot handle it? When two beans get the same name. Spring Boot 2.1+ forbids it by default (
BeanDefinitionOverrideException). Enable withspring.main.allow-bean-definition-overriding=true.
9. Glossary
| Term | Meaning |
|---|---|
| @Component | General bean marker annotation, registered by component scan |
| @Service | @Component specialization for business logic layer |
| @Repository | @Component specialization for data access + exception translation |
| @Configuration | Configuration class with CGLIB proxy |
| @Bean | Method-level bean definition in @Configuration class |
| @Primary | Designates the default implementation |
| @Qualifier | Selects a bean by name at injection point |
| BeanPostProcessor | Modifies bean instances in the lifecycle |
| BeanFactoryPostProcessor | Modifies bean definitions before instantiation |
| Component scanning | Automatic bean discovery by scanning packages |
| Exception translation | Converts SQL exceptions to DataAccessException (@Repository) |
| Lite mode | @Bean method in @Component, without CGLIB proxy |
| Bean overriding | Overriding a bean with the same name (disabled in Spring Boot) |
10. Cheatsheet
Stereotype annotations:
@Component → General bean
@Service → Business logic (semantic)
@Repository → Data access + exception translation
@Controller → Web layer + request mapping
@Bean vs @Component:
@Component = class-level, component scan
@Bean = method-level, @Configuration, third-party
Disambiguation:
@Primary → global default
@Qualifier("name") → explicit selection
Custom @Qualifier → type-safe selection
List<Interface> → all implementations
Lifecycle hooks:
BeanFactoryPostProcessor → modify bean definitions
BeanPostProcessor → modify bean instances
@PostConstruct → initialization after DI
@PreDestroy → cleanup (singleton only)
Bean pipeline:
Constructor → DI → BPP.before → @PostConstruct → BPP.after → Ready
Common mistakes:
✗ @Bean method in @Component (lite mode)
✗ @Qualifier string typo
✗ Multiple @Primary on same interface
✗ @Autowired in BeanPostProcessor
✗ DAO without @Repository
Spring Boot defaults:
Bean overriding = disabled (2.1+)
@Lazy = not default (eager init)
🎮 Games
10 questions