面试知识库
进阶

@Configuration代理机制#

一句话答案#

@Configuration 默认 proxyBeanMethods=true(Full 模式),Spring 用 CGLIB 给配置类生成代理,拦截 @Bean 方法的内部调用改为走容器,保证多次调用返回同一个单例;设为 false(Lite 模式)则不代理,@Bean 方法内部互调会真的执行方法、产生新对象。

核心要点

Full 模式(proxyBeanMethods=true,默认):

@Configuration  // 等价于 @Configuration(proxyBeanMethods = true)
public class AppConfig {
    @Bean
    public A a() { return new A(); }

    @Bean
    public B b() { return new B(a()); }  // 这里的 a() 不会 new 新对象,
                                         // CGLIB 代理拦截后从容器拿同一个单例 A
}
java
  • CGLIB 生成配置类子类,@Bean 方法被拦截:先查容器有没有该 Bean,有就直接返回,保证单例语义
  • 代价:要为每个配置类生成 CGLIB 代理,启动稍慢、有反射/继承开销

Lite 模式(proxyBeanMethods=false):

  • 不生成 CGLIB 代理,@Bean 方法就是普通方法
  • 上例中 a()真的执行并 new 出一个新的 A,b 持有的 A 与容器里的 A 不是同一个
  • 优点:启动更快、无 CGLIB 开销;SpringBoot 自动配置类大量用 proxyBeanMethods = false 优化启动

@Component 上写 @Bean 也是 Lite 模式(不拦截方法互调)。

判定是否需要 Full: 配置类里存在 @Bean 方法之间的内部调用且依赖单例语义 → 必须 Full;否则推荐 Lite。更稳妥的写法是直接用方法参数注入依赖,避免内部调用:public B b(A a) { return new B(a); },这样两种模式行为一致。

面试回答(2分钟版)

@Configuration 默认是 Full 模式,也就是 proxyBeanMethods 等于 true。这种模式下 Spring 会用 CGLIB 给配置类生成一个子类代理,把里面的 @Bean 方法都拦截掉。这样做的目的是保证 Bean 的单例语义:比如我在一个 @Bean 方法里调用了另一个 @Bean 方法,正常 Java 语义会执行方法体 new 一个新对象,但有了 CGLIB 代理拦截,它会先去容器里查这个 Bean 在不在,在的话直接返回容器里的单例,所以不会重复创建。代价是每个配置类都要生成 CGLIB 代理,启动会有一点开销。如果把 proxyBeanMethods 设成 false,就是 Lite 模式,不再代理,@Bean 方法就是普通方法,方法之间互相调用就会真的执行、产生新对象,所以这种模式只适合配置类内部 @Bean 方法之间没有互相调用的场景。SpringBoot 的自动配置类基本都显式写了 proxyBeanMethods = false,因为自动配置类非常多,关掉代理能明显加快启动速度。另外 @Component 上写 @Bean 也是 Lite 模式不会拦截。实际开发里最推荐的写法其实是 @Bean 方法之间通过方法参数注入依赖,而不是直接调用另一个 @Bean 方法,这样在 Full 和 Lite 两种模式下行为都是一致的,也避免踩坑。

追问与易错

追问方向:

  • “为什么 @Bean 方法内部互相调用不会创建多个实例?”→ Full 模式下 CGLIB 代理拦截了 @Bean 方法调用,拦截逻辑先查容器是否已有该 Bean,有则返回容器单例而不执行方法体
  • “proxyBeanMethods = false 有什么好处,为什么自动配置类都这么写?”→ 不生成 CGLIB 代理,省去代理创建和方法拦截开销,加快启动;自动配置类数量多且通常用参数注入依赖、不存在 @Bean 内部互调,所以可以安全关闭
  • “Lite 模式下怎么保证拿到的还是单例?”→ 不要在 @Bean 方法里直接调另一个 @Bean 方法,改用方法参数注入(如 b(A a)),由容器注入单例
  • “@Configuration 和 @Component 上放 @Bean 有区别吗?”→ @Configuration 默认 Full 会代理保证单例语义;@Component 是 Lite 不代理,@Bean 方法互调会产生新对象
  • “CGLIB 代理为什么要求 @Configuration 类不能是 final?”→ CGLIB 通过生成子类继承实现代理,final 类无法被继承,配置类和 @Bean 方法都不能是 final/private

易错点:

  • ❌ 以为 @Bean 方法互调一定返回单例——只有 Full 模式才拦截,Lite 模式会 new 新对象
  • ❌ 把配置类写成 final——CGLIB 无法继承,Full 模式会报错
  • ❌ 认为关掉 proxyBeanMethods 会破坏所有单例——单例由容器保证,只有”@Bean 方法内部调用”这种特殊写法才受影响