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