疑虑背景
疑虑形容
最近,在进行开发的过程中,发现之前的一个写法,相似如下
以我的了解,@Configuration 加 @Bean 会创立一个 userName 不为 null 的 UserManager 对象,而 @Component 也会创立一个 userName 为 null 的 UserManager 对象
那么咱们在其余对象中注入 UserManager 对象时,到底注入的是哪个对象?
因为我的项目曾经上线了很长一段时间了,所以这种写法没有编译报错,运行也没有出问题
前面去找共事理解下,理论是想让
失效,而理论也的确是它失效了
那么问题来了:Spring 容器中到底有几个 UserManager 类型的对象?
Spring Boot 版本
我的项目中用的 Spring Boot 版本是:2.0.3.RELEASE
对象的 scope 是默认值,也就是 singleton
后果验证
验证形式有很多,能够 debug 跟源码,看看 Spring 容器中到底有几个 UserManager 对象,也能够间接从 UserManager 构造方法下手,看看哪几个构造方法被调用,等等
咱们从构造方法下手,看看 UserManager 到底实例化了几次
只有有参构造方法被调用了,无参构造方法岿然不动(基本没被调用)
既然 UserManager 构造方法只被调用了一次,那么后面的问题:到底注入的是哪个对象
答案也就清晰了,没得选了呀,只能是 @Configuration 加 @Bean 创立的 userName 不为 null 的 UserManager 对象
问题又来了:为什么不是 @Component 创立的 userName 为 null 的 UserManager 对象?
源码解析
@Configuration 与 @Component 关系很严密
所以 @Configuration 可能被 component scan
其中 ConfigurationClassPostProcessor 与 @Configuration 非亲非故,其类继承结构图如下:
它实现了 BeanFactoryPostProcessor 接口和 PriorityOrdered 接口,对于 BeanFactoryPostProcessor,
那么咱们从 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 --- [
只是日志级别是 info,太不显眼了
Spring 降级优化
可能 Spring 团队意识到了 info 级别太不显眼的问题,或者说意识到了间接笼罩的解决形式不太正当
所以在 Spring 5.1.2.RELEASE(Spring Boot 则是 2.1.0.RELEASE)做出了优化解决
咱们来具体看看
启动间接报错,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 笼罩
Spring 4.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,我想大家应该曾经分明了。