The result of using @Bean and @Component on the same class

 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,可以看看:

www.cnblogs.com/youzhibing/…

那么我们从 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

Guess you like

Origin juejin.im/post/7086260976294576158