騰訊二面:@Bean 與 @Component 用在同一個類上,會怎么樣?
來源:cnblogs.com/youzhibing/p/15354706.html
疑慮背景
疑慮描述
最近,在進行開發(fā)的過程中,發(fā)現(xiàn)之前的一個寫法,類似如下

以我的理解,@Configuration?加?@Bean?會創(chuàng)建一個 userName 不為 null 的 UserManager 對象,而?@Component?也會創(chuàng)建一個 userName 為 null 的 UserManager 對象
那么我們在其他對象中注入 UserManager 對象時,到底注入的是哪個對象?
因為項目已經(jīng)上線了很長一段時間了,所以這種寫法沒有編譯報錯,運行也沒有出問題
后面去找同事了解下,實際是想讓

生效,而實際也確實是它生效了
那么問題來了:Spring 容器中到底有幾個 UserManager 類型的對象?
Spring Boot 版本
項目中用的 Spring Boot 版本是:2.0.3.RELEASE
對象的 scope 是默認值,也就是 singleton
結(jié)果驗證
驗證方式有很多,可以 debug 跟源碼,看看 Spring 容器中到底有幾個 UserManager 對象,也可以直接從 UserManager 構(gòu)造方法下手,看看哪幾個構(gòu)造方法被調(diào)用,等等
我們從構(gòu)造方法下手,看看 UserManager 到底實例化了幾次

只有有參構(gòu)造方法被調(diào)用了,無參構(gòu)造方法巋然不動(根本沒被調(diào)用)
果想了解的更深一點,可以讀讀:Spring 的循環(huán)依賴,源碼詳細分析 → 真的非要三級緩存嗎
既然 UserManager 構(gòu)造方法只被調(diào)用了一次,那么前面的問題:到底注入的是哪個對象
答案也就清晰了,沒得選了呀,只能是?@Configuration?加?@Bean?創(chuàng)建的 userName 不為 null 的 UserManager 對象
問題又來了:為什么不是?@Component?創(chuàng)建的 userName 為 null 的 UserManager 對象?
源碼解析
@Configuration?與?@Component?關系很緊密

所以@Configuration?能夠被?component scan
其中?ConfigurationClassPostProcessor?與@Configuration?息息相關,其類繼承結(jié)構(gòu)圖如下:

它實現(xiàn)了?BeanFactoryPostProcessor?接口和?PriorityOrdered?接口,關于?BeanFactoryPostProcessor,可以看看:
https://www.cnblogs.com/youzhibing/p/10559337.html
那么我們從?AbstractApplicationContext?的 refresh 方法調(diào)用的?invokeBeanFactoryPostProcessors(beanFactory)開始,來跟下源碼

此時完成了?com.lee.qsl?包下的?component scan?,?com.lee.qsl?包及子包下的 UserConfig 、 UserController 和 UserManager 都被掃描出來
注意,此刻@Bean?的處理還未開始, UserManager 是通過@Component?而被掃描出來的;此時 Spring 容器中?beanDefinitionMap?中的 UserManager 是這樣的

接下來一步很重要,與我們想要的答案息息相關


循環(huán)遞歸處理 UserConfig 、 UserController 和 UserManager ,把它們都封裝成?ConfigurationClass?,遞歸掃描?BeanDefinition
循環(huán)完之后,我們來看看?configClasses

UserConfig bean?定義信息中 beanMethods 中有一個元素 [BeanMethod:name=userManager,declaringClass=com.lee.qsl.config.UserConfig]
然后我們接著往下走,來仔細看看答案出現(xiàn)的環(huán)節(jié)

是不是有什么發(fā)現(xiàn)?@Component?修飾的 UserManager 定義直接被覆蓋成了?@Configuration + @Bean?修飾的 UserManager 定義
Bean 定義類型也由?ScannedGenericBeanDefinition?替換成了?ConfigurationClassBeanDefinition
后續(xù)通過?BeanDefinition?創(chuàng)建實例的時候,創(chuàng)建的自然就是?@Configuration + @Bean?修飾的 UserManager ,也就是會反射調(diào)用 UserManager 的有參構(gòu)造方法
自此,答案也就清楚了
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 升級優(yōu)化
可能 Spring 團隊意識到了 info 級別太不顯眼的問題,或者說意識到了直接覆蓋的處理方式不太合理
所以在?Spring 5.1.2.RELEASE?(Spring Boot 則是 2.1.0.RELEASE )做出了優(yōu)化處理
我們來具體看看

啟動直接報錯,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?的區(qū)別

新增了配置項?allowBeanDefinitionOverriding?來控制是否允許?BeanDefinition?覆蓋,默認情況下是不允許的
我們可以在配置文件中配置:spring.main.allow-bean-definition-overriding=true?,允許?BeanDefinition?覆蓋
這種處理方式是更優(yōu)的,將選擇權交給開發(fā)人員,而不是自己偷偷的處理,已達到開發(fā)者想要的效果
總結(jié)
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?做出了優(yōu)化處理
增加了配置項:allowBeanDefinitionOverriding?,將主動權交給了開發(fā)者,由開發(fā)者自己決定是否允許覆蓋
補充
關于?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??,我想大家應該已經(jīng)清楚了
