@Bean 與 @Component 用在同一個(gè)類上,會(huì)怎么樣?【文末贈(zèng)書(shū)活動(dòng)】
關(guān)注▼Java學(xué)習(xí)之道▼一起成長(zhǎng),一起學(xué)習(xí)~
作者: 青石路
來(lái)源: https://www.cnblogs.com/youzhibing/p/15354706.html
一、疑慮背景
疑慮描述
最近,在進(jìn)行開(kāi)發(fā)的過(guò)程中,發(fā)現(xiàn)之前的一個(gè)寫法,類似如下

以我的理解,@Configuration?加?@Bean?會(huì)創(chuàng)建一個(gè) userName 不為 null 的 UserManager 對(duì)象,而?@Component?也會(huì)創(chuàng)建一個(gè) userName 為 null 的 UserManager 對(duì)象
那么我們?cè)谄渌麑?duì)象中注入 UserManager 對(duì)象時(shí),到底注入的是哪個(gè)對(duì)象?
因?yàn)轫?xiàng)目已經(jīng)上線了很長(zhǎng)一段時(shí)間了,所以這種寫法沒(méi)有編譯報(bào)錯(cuò),運(yùn)行也沒(méi)有出問(wèn)題
后面去找同事了解下,實(shí)際是想讓

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

只有有參構(gòu)造方法被調(diào)用了,無(wú)參構(gòu)造方法巋然不動(dòng)(根本沒(méi)被調(diào)用)
既然 UserManager 構(gòu)造方法只被調(diào)用了一次,那么前面的問(wèn)題:到底注入的是哪個(gè)對(duì)象
答案也就清晰了,沒(méi)得選了呀,只能是?@Configuration?加?@Bean?創(chuàng)建的 userName 不為 null 的 UserManager 對(duì)象
問(wèn)題又來(lái)了:為什么不是?@Component?創(chuàng)建的 userName 為 null 的 UserManager 對(duì)象?
三、源碼解析
@Configuration?與?@Component?關(guān)系很緊密

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

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

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

接下來(lái)一步很重要,與我們想要的答案息息相關(guān)


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

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

是不是有什么發(fā)現(xiàn)?@Component?修飾的 UserManager 定義直接被覆蓋成了?@Configuration + @Bean?修飾的 UserManager 定義
Bean 定義類型也由?ScannedGenericBeanDefinition?替換成了?ConfigurationClassBeanDefinition
后續(xù)通過(guò)?BeanDefinition?創(chuàng)建實(shí)例的時(shí)候,創(chuàng)建的自然就是?@Configuration + @Bean?修飾的 UserManager ,也就是會(huì)反射調(diào)用 UserManager 的有參構(gòu)造方法
自此,答案也就清楚了。歡迎關(guān)注公眾號(hào)"Java學(xué)習(xí)之道",查看更多干貨!
Spring 其實(shí)給出了提示:
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]]
只是日志級(jí)別是 info ,太不顯眼了
四、Spring 升級(jí)優(yōu)化
可能 Spring 團(tuán)隊(duì)意識(shí)到了 info 級(jí)別太不顯眼的問(wèn)題,或者說(shuō)意識(shí)到了直接覆蓋的處理方式不太合理
所以在?Spring 5.1.2.RELEASE?(Spring Boot 則是 2.1.0.RELEASE )做出了優(yōu)化處理
我們來(lái)具體看看

啟動(dòng)直接報(bào)錯(cuò),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.
我們來(lái)跟下源碼,主要看看與?Spring 5.0.7.RELEASE?的區(qū)別

新增了配置項(xiàng)?allowBeanDefinitionOverriding?來(lái)控制是否允許?BeanDefinition?覆蓋,默認(rèn)情況下是不允許的
我們可以在配置文件中配置:spring.main.allow-bean-definition-overriding=true?,允許?BeanDefinition?覆蓋
這種處理方式是更優(yōu)的,將選擇權(quán)交給開(kāi)發(fā)人員,而不是自己偷偷的處理,已達(dá)到開(kāi)發(fā)者想要的效果
五、總結(jié)
Spring 5.0.7.RELEASE?(?Spring Boot 2.0.3.RELEASE?) 支持@Configuration + @Bean?與@Component?同時(shí)作用于同一個(gè)類
啟動(dòng)時(shí)會(huì)給 info 級(jí)別的日志提示,同時(shí)會(huì)將@Configuration + @Bean?修飾的 BeanDefinition 覆蓋掉@Component?修飾的 BeanDefinition
也許 Spring 團(tuán)隊(duì)意識(shí)到了上述處理不太合適,于是在?Spring 5.1.2.RELEASE?做出了優(yōu)化處理
增加了配置項(xiàng):allowBeanDefinitionOverriding?,將主動(dòng)權(quán)交給了開(kāi)發(fā)者,由開(kāi)發(fā)者自己決定是否允許覆蓋
六、補(bǔ)充
關(guān)于?allowBeanDefinitionOverriding?,前面講的不對(duì),后面特意去翻了下源碼,補(bǔ)充如下
Spring 1.2 引進(jìn)?DefaultListableBeanFactory?的時(shí)候就有了?private boolean allowBeanDefinitionOverriding = true;,默認(rèn)是允許?BeanDefinition?覆蓋

Spring 4.1.2 引進(jìn)了?isAllowBeanDefinitionOverriding()方法

Spring 自始至終默認(rèn)都是允許?BeanDefinition?覆蓋的,變的是 Spring Boot , Spring Boot 2.1.0 之前沒(méi)有覆蓋 Spring 的?allowBeanDefinitionOverriding?默認(rèn)值,仍是允許?BeanDefinition?覆蓋的
Spring Boot 2.1.0 中 SpringApplication 定義了私有屬性:allowBeanDefinitionOverriding

沒(méi)有顯示的指定值,那么默認(rèn)值就是 false ,之后在 Spring Boot 啟動(dòng)過(guò)程中,會(huì)用此值覆蓋掉 Spring 中的?allowBeanDefinitionOverriding?的默認(rèn)值

關(guān)于?allowBeanDefinitionOverriding??,我想大家應(yīng)該已經(jīng)清楚了。
本次贈(zèng)送的書(shū)籍有以下三本可選擇:

免費(fèi)獲取方法:
開(kāi)獎(jiǎng)日期 5月15日 12:00,開(kāi)獎(jiǎng)前均可在公眾號(hào)后臺(tái)回復(fù)?【?java學(xué)習(xí)?】關(guān)鍵詞參與活動(dòng)?。?!
▲點(diǎn)擊進(jìn)入回復(fù)「java學(xué)習(xí)」抽獎(jiǎng)品 沒(méi)加小編微信的建議先加一下小編微信,方便中獎(jiǎng)之后安排發(fā)貨

