bigywater.tech
Spring · 8

@Configuration 的 CGLIB 增强与 lite 模式:@Bean 方法互相调用

@Configuration 里一个 @Bean 方法调用另一个 @Bean 方法,为什么能拿到同一个单例?把 proxyBeanMethods 设成 false 又会怎样?什么时候会悄悄多创建对象?代价有多大?这篇在 Spring Framework 7.0.9 上逐项量出来。

1 对 3
同样的互相调用,full 模式与 lite 模式里 A 被构造的次数
847 → 475 ms
300 个配置类(1500 个 Bean)把 proxyBeanMethods 设成 false 后 refresh 的中位数耗时
1174 个
full 模式比 lite 模式 refresh 期间多加载的类(3196 对 2022)

full 与 lite

实测 · 本机执行输出
  (a)@Configuration(默认,full 模式):A 被构造了 1 次;b1 和 b2 持有同一个 A(单例语义保住了);容器里 A 类型的 Bean 数 1;配置类的实际类型 Main$Full$$SpringCGLIB$$…(CGLIB 子类)
  (b)@Configuration(proxyBeanMethods = false):A 被构造了 3 次;b1 和 b2 持有不同的 A(2 个,单例语义没了);容器里 A 类型的 Bean 数 1;配置类的实际类型 Lite(原类)
  (c)@Component 里写 @Bean(lite 模式):A 被构造了 3 次;b1 和 b2 持有不同的 A(2 个,单例语义没了);容器里 A 类型的 Bean 数 1;配置类的实际类型 AsComponent(原类)
  (d)lite 模式但用方法参数注入:A 被构造了 1 次;b1 和 b2 持有同一个 A(单例语义保住了);容器里 A 类型的 Bean 数 1;配置类的实际类型 LiteParam(原类)
  (e)full 模式,但 a() 是 static @Bean:A 被构造了 3 次;b1 和 b2 持有不同的 A(2 个,单例语义没了);容器里 A 类型的 Bean 数 1;配置类的实际类型 Main$StaticBean$$SpringCGLIB$$…(CGLIB 子类)
  (f)final 的 @Configuration 类 → BeanDefinitionParsingException: Configuration problem: @Configuration class 'Main.Fin' may not be final. Remove the final modifier to continue.

full 模式里配置类被 CGLIB 子类替换,@Bean 方法互相调用被拦截成 getBean,单例语义保持;@Component 里的 @Bean、proxyBeanMethods = false、static @Bean 都是普通 Java 调用,会多构造对象,改用方法参数注入最安全。

启动代价

实测 · 本机执行输出
  配置  |  refresh 耗时 ms  |  新加载的类数  |  BeanDefinition 数  |  配置类被 CGLIB 增强
  proxyBeanMethods = true(CGLIB 增强)  |  847 (826~995)  |  3196 (3196~3196)  |  1805  |  true
  proxyBeanMethods = false  |  475 (468~497)  |  2022 (2022~2022)  |  1805  |  false
  full 模式给每个配置类生成一个 CGLIB 子类,启动多花时间、多加载类(多出的类数比 300 个子类还多,多出来的是什么我没有细查)
  @AutoConfiguration 上的元注解 @Configuration 的 proxyBeanMethods = false
  spring-boot-autoconfigure 的 AutoConfiguration.imports 里登记了 12 个自动配置类(Boot 4 的自动配置大多放在各个功能模块自己的 jar 里,其它模块我没有统计)

CGLIB 增强要为每个配置类生成并加载子类,300 个配置类多花约 372 ms、多加载 1174 个类。

分步演示

@Configuration 的 CGLIB 增强
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9;耗时是每种配置各启动 7 个 JVM 的中位数,括号里是最小和最大;第二节的类是用脚本生成的 300 个配置类,每类 5 个返回 new Object() 的 @Bean 方法
    • 没有验证:多出来的 1174 个类具体是什么;Boot 里组件扫描到的 @Configuration 与自动配置对 proxyBeanMethods 的不同处理;AOT 模式下配置类的处理;@Bean 方法互调在 Kotlin 或 Groovy 下的行为;proxyBeanMethods 对运行期内存占用的影响
    京ICP备2026067502号-1京公网安备11010502063487号