Why @Configuration Creates Proxies in Spring and Guarantees Singleton Beans
This article explains how Spring's @Configuration annotation triggers CGLIB proxy generation to manage @Bean methods, ensuring that internal calls return the same singleton instance, and contrasts this behavior with using @Component for configuration classes.
@Configuration is a class‑level annotation that marks a class as a source of bean definitions; methods annotated with @Bean declare beans, effectively replacing XML configuration and allowing flexible registration in the IoC container.
Simple Example
<code>static class Person {}
@Configuration
static class AppConfig {
@Bean
public Person person() {
return new Person();
}
}
</code>Using @Component instead of @Configuration changes the behavior:
<code>@Component
static class AppConfig {
@Bean
public Person person() {
return new Person();
}
}
try (GenericApplicationContext context = new GenericApplicationContext()) {
context.registerBean(AppConfig.class);
System.out.println(context.getBean(Person.class));
}
</code>When the class is annotated with @Configuration , multiple calls to person() return the same instance, while with @Component they return different instances, revealing the importance of the proxy mechanism.
How Spring Guarantees a Single Instance
Spring registers a ConfigurationClassPostProcessor , which is a BeanFactoryPostProcessor . It scans for @Configuration classes and marks them for proxy creation.
<code>// proxyBeanMethods = true means a proxy will be generated
@Configuration(proxyBeanMethods = true)
static class AppConfig {}
</code>The marking is performed by ConfigurationClassUtils , which sets the attribute CONFIGURATION_CLASS_FULL (full) or CONFIGURATION_CLASS_LITE (lite) on the bean definition.
<code>if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
} else {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
</code>During postProcessBeanFactory , Spring enhances the marked configuration classes by generating a CGLIB subclass:
<code>public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
// collect full‑configuration beans
// create proxy subclass via ConfigurationClassEnhancer
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
beanDef.setBeanClass(enhancedClass);
}
}
</code>The enhancer sets up callbacks, the most important being BeanMethodInterceptor :
<code>private static class BeanMethodInterceptor implements MethodInterceptor {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] args, MethodProxy proxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
return resolveBeanReference(beanMethod, args, beanFactory, beanName);
}
}
</code>This interceptor looks up the bean by name in the container and returns the existing singleton, so internal method calls never create a new instance.
Consequently, using @Configuration ensures that all @Bean methods are proxied and that the container always provides the same bean instance, whereas @Component lacks this proxying, leading to multiple instances.
In summary, Spring’s proxy‑based mechanism, driven by ConfigurationClassPostProcessor , ConfigurationClassUtils , and ConfigurationClassEnhancer , guarantees the singleton semantics of beans defined in @Configuration classes.
Did you learn something?
Finished!!!
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.