Understanding @Configuration vs @Component in Spring: Bean Creation and Proxies
This article explains the distinct roles of Spring's @Configuration and @Component annotations, demonstrates their behavior through code examples, compares bean initialization and proxy generation, and reveals how Spring's ConfigurationClassPostProcessor and related enhancers ensure singleton beans across method calls.
Environment: Spring 6.1.2
1. Introduction
@Configuration and @Component have different purposes and usage scenarios in the Spring framework.
@Component
@Component is a generic Spring annotation that marks a class as a component. It tells Spring to treat the class as a regular bean, to be discovered during class‑path scanning, instantiated, and registered in the container.
@Configuration
@Configuration marks a class as a configuration class. Unlike ordinary components, it primarily contains @Bean methods that define bean creation. During startup, Spring invokes these @Bean methods and registers the returned objects as beans, allowing fine‑grained control over dependency injection and component assembly.
2. Differences
2.1 Environment Preparation
<code>public class PersonDAO {}
public class PersonService {}
public class AppConfig {
@Bean
public PersonDAO personDAO() {
return new PersonDAO();
}
@Bean
public PersonService personService() {
return new PersonService();
}
}
</code>The above classes are used for testing.
2.2 Difference 1 – Using @Configuration
<code>@Configuration
public class AppConfig {
// ...
}
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println(context.getBean(AppConfig.class));
System.out.println(context.getBean(PersonDAO.class));
}
</code>Output:
<code>com.pack.AppConfig$$SpringCGLIB$$0@17f7cd29
com.pack.PersonDAO@7d8704ef
</code>AppConfig is a proxy class.
2.2 Difference 1 – Using @Component
<code>@Component
static class AppConfig {
// ...
}
</code>Output:
<code>com.pack.AppConfig@659a969b
com.pack.PersonDAO@76908cc0
</code>AppConfig is a regular class, not a proxy.
2.3 Difference 2 – Inter‑@Bean Method Calls
When @Bean methods call each other inside a @Configuration class, Spring ensures the same singleton instance is returned.
<code>@Configuration
static class AppConfig {
@Bean
PersonDAO personDAO() {
PersonService ps = personService();
System.out.printf("In @Bean method get PersonService: %s%n", ps);
return new PersonDAO();
}
@Bean
PersonService personService() {
return new PersonService();
}
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.printf("Get PersonService: %s%n", context.getBean(PersonService.class));
</code>Output:
<code>In @Bean method get PersonService: com.pack.PersonService@2d1ef81a
Get PersonService: com.pack.PersonService@2d1ef81a
</code>The same PersonService instance is returned, guaranteeing singleton behavior.
When the same class is annotated with @Component instead of @Configuration, the @Bean methods are not proxied, leading to different instances:
<code>In @Bean method get PersonService: com.pack.PersonService@3901d134
Get PersonService: com.pack.PersonService@4c9f8c13
</code>This can cause multiple bean instances, potentially breaking transaction management or other container‑level features.
2.4 Changing @Configuration Proxy Mode
Setting proxyBeanMethods = false disables CGLIB proxying for @Bean methods.
<code>@Configuration(proxyBeanMethods = false)
static class AppConfig {}
</code>Running the previous example with this setting yields different instances, similar to the @Component case.
3. @Configuration Internals
The annotation is processed by ConfigurationClassPostProcessor , a BeanFactoryPostProcessor that scans for @Configuration candidates and enhances them.
<code>public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
// 1. During container initialization, process configuration bean definitions
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
processConfigBeanDefinitions(registry);
}
// 2. Enhance bean definitions to create CGLIB proxies
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
private void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
// Iterate over bean definitions, identify full configuration classes, and replace them with enhanced subclasses
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
Class<?> configClass = entry.getValue().getBeanClass();
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
entry.getValue().setBeanClass(enhancedClass);
}
}
}
}
</code>The ConfigurationClassEnhancer creates a CGLIB subclass and registers three callbacks, the first of which is BeanMethodInterceptor . This interceptor ensures that each call to an @Bean method retrieves the bean from the container, preserving singleton semantics.
Thus, the proxy mechanism behind @Configuration guarantees that inter‑method calls within the configuration class always return the same bean instance.
In summary, both @Configuration and @Component can contain @Bean methods, but only @Configuration classes are proxied by Spring, which enforces singleton behavior across @Bean method invocations.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.