@Configuration 类为何是 CGLIB 代理
结论先行:@Configuration 默认 proxyBeanMethods = true,Spring 会通过 CGLIB 为配置类生成子类代理,拦截 @Bean 方法之间的内部调用,保证返回的是容器里同一个单例 Bean(不会因方法互调而重复 new)。若显式写 @Configuration(proxyBeanMethods = false)(lite 模式),@Bean 退化为普通工厂方法,内部互调就会每次新建对象、单例语义被破坏。
一、原理与目的
- 配置类在容器刷新早期由 ConfigurationClassPostProcessor 处理:注册为 Bean 并生成 CGLIB 代理(代理实现 EnhancedConfiguration 接口);
- 代理拦截对 @Bean 方法的调用:先去容器查该 Bean 是否已存在(BeanFactory.getBean 语义),存在直接返回容器实例,不存在则执行方法创建后缓存——所以"配置即代码"里方法互调拿到的是容器级单例;
- 代价(CGLIB 约束):配置类不能是 final,@Bean 方法不能被 final/static 修饰,类需可被子类化。
二、代码演示
@Configuration // proxyBeanMethods 默认 true
public class AppConfig {
@Bean
public DataSourceA dataSourceA() { return new DataSourceA(); }
@Bean
public RepositoryB repositoryB() {
return new RepositoryB(dataSourceA()); // 内部互调:被代理拦截 → 返回容器中同一个 A
}
}
// 对比:@Configuration(proxyBeanMethods = false) 时,上面互调每次 new DataSourceA(),
// repositoryB 与外部注入的 dataSourceA 是两个不同实例 → 单例失效
常见追问 / 记忆点
- 追问 1:什么时候该关 proxyBeanMethods?答:@Bean 方法之间从不互调、或刻意要"每次现取"时(如 Boot 部分内部自动配置类用 false 省去代理开销),业务配置类一般保持默认 true。
- 追问 2:静态 @Bean 方法会被拦截吗?答:静态方法属于类、无法被子类实例方法覆写拦截,所以内部互调不走容器,这也是官方不建议配置类 @Bean 写 static 的原因之一。
- 追问 3:与 @Component 类的区别?答:普通 @Component 不会做这种 CGLIB 代理,只有 @Configuration(含 @Bean 方法)才会被 ConfigurationClassPostProcessor 增强。
- 追问 4:如何验证配置类被代理?答:运行时打印配置类对象的 getClass(),会看到形如 AppConfig$$SpringCGLIB$$0 的 CGLIB 子类类名。
- 追问 5:普通 @Component 类里的 @Bean 方法会被拦截吗?答:不会——非 @Configuration 类中的 @Bean 按 lite 模式处理,不做 CGLIB 代理,内部互调会直接执行原方法。
- 记忆点:"@Configuration 里 @Bean 互调 = 走容器 = 单例一致,靠 CGLIB 拦截实现;要裸调用就关 proxyBeanMethods"。