起源:cnblogs.com/youzhibing/p/15354706.html
疑虑背景
疑虑形容
最近,在进行开发的过程中,发现之前的一个写法,相似如下:
以我的了解,@Configuration 加 @Bean 会创立一个 userName 不为 null 的 UserManager 对象,而 @Component 也会创立一个 userName 为 null 的 UserManager 对象。
那么咱们在其余对象中注入 UserManager 对象时,到底注入的是哪个对象?
因为我的项目曾经上线了很长一段时间了,所以这种写法没有编译报错,运行也没有出问题。前面去找共事理解下,理论是想让:
失效,而理论也的确是它失效了。那么问题来了:Spring 容器中到底有几个 UserManager 类型的对象?
Spring Boot 版本
我的项目中用的 Spring Boot 版本是:2.0.3.RELEASE。对象的 scope 是默认值,也就是 singleton。
Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://github.com/javastacks...
后果验证
验证形式有很多,能够 debug 跟源码,看看 Spring 容器中到底有几个 UserManager 对象,也能够间接从 UserManager 构造方法下手,看看哪几个构造方法被调用,等等。
咱们从构造方法下手,看看 UserManager 到底实例化了几次。
只有有参构造方法被调用了,无参构造方法岿然不动(基本没被调用)。如果想理解的更深一点,能够读读:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗?
https://www.cnblogs.com/youzh...
既然 UserManager 构造方法只被调用了一次,那么后面的问题:到底注入的是哪个对象。答案也就清晰了,没得选了呀,只能是 @Configuration 加 @Bean 创立的 userName 不为 null 的 UserManager 对象。
问题又来了:为什么不是 @Component 创立的 userName 为 null 的 UserManager 对象?
源码解析
@Configuration 与 @Component 关系很严密。
所以@Configuration可能被component scan。
其中 ConfigurationClassPostProcessor与@Configuration 非亲非故,其类继承结构图如下:
它实现了BeanFactoryPostProcessor接口和PriorityOrdered接口。
对于 BeanFactoryPostProcessor,能够看看:
https://www.cnblogs.com/youzh...
从AbstractApplicationContext的refresh办法调用的invokeBeanFactoryPostProcessors(beanFactory)开始,来跟下源码。
此时实现了com.lee.qsl包下的component scan ,com.lee.qsl包及子包下的 UserConfig、UserController和UserManager都被扫描进去。留神,此刻@Bean的解决还未开始,UserManager是通过@Component而被扫描进去的;此时Spring容器中beanDefinitionMap中的 UserManager是这样的。
接下来一步很重要,与咱们想要的答案非亲非故。
循环递归解决UserConfig 、UserController和UserManager,把它们都封装成 ConfigurationClass ,递归扫描 BeanDefinition。循环完之后,咱们来看看 configClasses。
UserConfig bean定义中beanMethods中有一个元素 [BeanMethod:name=userManager,declaringClass=com.lee.qsl.config.UserConfig]。
而后咱们接着往下走,来认真看看答案呈现的环节。
是不是有什么发现?@Component润饰的UserManager定义间接被笼罩成了@Configuration +@Bean润饰的UserManager定义。Bean定义类型也由ScannedGenericBeanDefinition替换成了ConfigurationClassBeanDefinition。
后续通过BeanDefinition创立实例的时候,创立的天然就是@Configuration+@Bean润饰的 UserManager,也就是会反射调用UserManager的有参构造方法。
自此,答案也就分明了。Spring 其实给出了提醒:
2021-10-03 20:37:33.697 INFO 13600 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'userManager' with a different definition: replacing [Generic bean: class [com.lee.qsl.manager.UserManager]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=userConfig; factoryMethodName=userManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/lee/qsl/config/UserConfig.class]]
只是日志级别是 info ,太不显眼了。
Spring降级优化
可能Spring团队意识到了info级别太不显眼的问题,或者说意识到了间接笼罩的解决形式不太正当。所以在Spring 5.1.2.RELEASE (Spring Boot 则是 2.1.0.RELEASE )做出了优化解决。
举荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks...
咱们来具体看看。
启动间接报错,Spring也给出了提醒。
The bean 'userManager', defined in class path resource [com/lee/qsl/config/UserConfig.class], could not be registered. A bean with that name has already been defined in file [D:\qsl-project\spring-boot-bean-component\target\classes\com\lee\qsl\manager\UserManager.class] and overriding is disabled.
咱们来跟下源码,次要看看与Spring 5.0.7.RELEASE的区别。
新增了配置项allowBeanDefinitionOverriding来管制是否容许BeanDefinition笼罩,默认状况下是不容许的。咱们能够在配置文件中配置:spring.main.allow-bean-definition-overriding=true ,容许BeanDefinition笼罩。这种解决形式是更优的,将选择权交给开发人员,而不是本人偷偷的解决,已达到开发者想要的成果。
总 结
Spring 5.0.7.RELEASE ( Spring Boot 2.0.3.RELEASE )反对@Configuration+ @Bean与@Component同时作用于同一个类。启动时会给info级别的日志提醒,同时会将@Configuration+@Bean润饰的 BeanDefinition笼罩掉@Component润饰的BeanDefinition。
兴许Spring团队意识到了上述解决不太适合,于是在Spring 5.1.2.RELEASE做出了优化解决。减少了配置项:allowBeanDefinitionOverriding,将主动权交给了开发者,由开发者本人决定是否容许笼罩。
补充
对于allowBeanDefinitionOverriding,后面有误,特意去翻了下源码,补充如下。Spring 1.2引进DefaultListableBeanFactory的时候就有了private boolean allowBeanDefinitionOverriding=true;,默认是容许BeanDefinition笼罩。
Spring4.1.2引进isAllowBeanDefinitionOverriding()办法。
Spring从头至尾默认都是容许BeanDefinition笼罩的,变的是Spring Boot ,Spring Boot 2.1.0之前没有笼罩Spring 的allowBeanDefinitionOverriding默认值,仍是容许BeanDefinition笼罩的。
Spring Boot 2.1.0中SpringApplication定义了公有属性:allowBeanDefinitionOverriding。
没有显示的指定值,那么默认值就是false ,之后在Spring Boot启动过程中,会用此值笼罩掉Spring中的allowBeanDefinitionOverriding的默认值。
对于allowBeanDefinitionOverriding,我想大家应该曾经分明了。
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!