Doubt Background
Description of concerns
Recently, in the process of development, I found a previous writing, similar to the following
In my understanding, @Configuration
Plus @Bean
will create a UserManager object whose userName is not null, but @Component
also create a UserManager object whose userName is null
So when we inject the UserManager object into other objects, which object is injected?
Because the project has been online for a long time, there is no compilation error in this way of writing, and there is no problem in operation.
Go to my colleagues later to find out, actually want to let
takes effect, and in fact it does take effect
So here comes the question: How many objects of type UserManager are there in the Spring container?
Spring Boot version
The Spring Boot version used in the project is:2.0.3.RELEASE
The scope of the object is the default value, which is a singleton
Result verification
There are many verification methods. You can debug and source code to see how many UserManager objects are in the Spring container. You can also start directly from the UserManager constructor to see which constructors are called, etc.
Let's start with the constructor and see how many times UserManager is instantiated
Only the parameterized constructor is called, and the parameterless constructor remains untouched (not called at all)
Since the UserManager constructor is called only once, the previous question: which object is injected
The answer is also clear, there is no choice, it can only be @Configuration
added to the @Bean
created UserManager object whose userName is not null
问题又来了:为什么不是 @Component
创建的 userName 为 null 的 UserManager 对象? 搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!
源码解析
@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 的有参构造方法
自此,答案也就清楚了。搜索公纵号:MarkerHub,关注回复[ vue ]获取前后端入门教程!
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 也给出了提示
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
同时作用于同一个类
At startup, a log prompt of info level will be given, and @Configuration + @Bean
the decorated BeanDefinition will be overwritten by @Component
the decorated BeanDefinition.
Maybe the Spring team realized that the above processing is not suitable, so they Spring 5.1.2.RELEASE
made optimization processing
Added the configuration item: allowBeanDefinitionOverriding
, the initiative is handed over to the developer, and the developer decides whether to allow overwriting
Replenish
Regarding allowBeanDefinitionOverriding
, what I said above is wrong, I deliberately went through the source code later, and added the following
When Spring 1.2 DefaultListableBeanFactory
was introduced private boolean allowBeanDefinitionOverriding = true;
, the default is to allow BeanDefinition
overriding
Spring 4.1.2 introduced isAllowBeanDefinitionOverriding()
methods
Spring has been allowed to override by default from beginning to end BeanDefinition
, but Spring Boot has changed. Spring Boot 2.1.0 did not override the allowBeanDefinitionOverriding
default value of Spring, but it is still allowed to BeanDefinition
override
SpringApplication in Spring Boot 2.1.0 defines private properties:allowBeanDefinitionOverriding
If there is no specified value displayed, then the default value is false, and then during the Spring Boot startup process, this value will be used to override allowBeanDefinitionOverriding
the default value in Spring
About allowBeanDefinitionOverriding
, I think everyone should be clear