關(guān)于spring的@Bean注解放入引用Bean中初始化失敗分析
以下討論的問(wèn)題及術(shù)語(yǔ)均在SpringBoot框架下,問(wèn)題十分小眾,僅做整理記錄。
1. Bean依賴屬性
Bean依賴屬性的注入順序,與代碼定義順序無(wú)關(guān);
最好是將@Bean
注解配置的Bean放在@Configuration
注解修飾的專門(mén)用于配置的類中;
2. 問(wèn)題背景
為了方便,將使用注解(@Bean
)方法生成的Bean的方法體定義在了使用此Bean的類中
代碼結(jié)構(gòu)如下(為了描述方便,后文我們姑且將initBeanTestService
叫做外層Bean,needInitBean
叫做內(nèi)層Bean):
編寫(xiě)單元測(cè)試
運(yùn)行printInitBeanValue
方法,并在方法體內(nèi)打斷點(diǎn)便于觀察屬性值,
單元測(cè)試:
運(yùn)行單元測(cè)試會(huì)發(fā)現(xiàn),通過(guò)內(nèi)層Bean的屬性值needInitValue
的值為null
,而外層Bean的屬性值needInitValue
有值
說(shuō)明在初始化needInitBean
時(shí),外層Bean的屬性值initValue
并未注入成功,
運(yùn)行結(jié)果:
簡(jiǎn)單理下思路,因?yàn)橥鈱覤ean的類通過(guò)@Service
注解進(jìn)行修飾,所以SpringBoot在啟動(dòng)時(shí)會(huì)掃描到此注解進(jìn)行Bean的初始化
初始化時(shí)會(huì)發(fā)現(xiàn)此Bean依賴initValue
和neeInitBean
兩個(gè)屬性,讀配置拿到initValue
的值
然后去容器中查找是否有needInitBean
存在,顯然并不存在,于是要先初始化needInitBean
,即內(nèi)層Bean;
內(nèi)層bean的初始化,依賴于外層bean的initValue
屬性值
從現(xiàn)象來(lái)看,此時(shí)initValue
無(wú)值,我們有以下疑問(wèn):
此initValue為什么沒(méi)有值?外層Bean按理說(shuō)應(yīng)該已經(jīng)初始化一半了。
3. 調(diào)用棧追蹤
為了解釋上述問(wèn)題1,我們?cè)?code>@Bean注解修飾的方法體內(nèi)打斷點(diǎn),從內(nèi)層Bean的初始化開(kāi)始,沿著斷點(diǎn)處的調(diào)用棧倒著追蹤,
1.首先是一些反射包下的方法;
2.一些BeanFactory初始化bean的方法;
3.找到AbstractBeanFactory
中,發(fā)現(xiàn)此處開(kāi)始創(chuàng)建needInitBean
,那么上邊的調(diào)用方就是初始化此Bean的觸發(fā)點(diǎn);
4.找到CommonAnnotationBeanPostProcessor
,發(fā)現(xiàn)是此處為觸發(fā)點(diǎn);
5.在CommonAnnotationBeanPostProcessor
一番游歷,發(fā)現(xiàn)此處的邏輯是向外層Bean中注入依賴,找到319行,findResourceMetadata
,此方法為找到需要注入的屬性或方法的元數(shù)據(jù),緊接著321行,為依賴注入邏輯(當(dāng)然,若依賴是Bean,則去BeanFactory請(qǐng)求,找不到則進(jìn)行初始化);
點(diǎn)進(jìn)去findResourceMetadata
方法看看他是咋找要注入的屬性的,包了一層緩存,主要邏輯在buildResourceMetadata
方法,這里我們會(huì)發(fā)現(xiàn),他遍歷了各個(gè)屬性和方法,找到有特定注解的屬性和方法,放到了待注入的列表。其中注解就包括了我們熟悉的,也是外層bean中needInitBean
頭上的@Resource
。但是并沒(méi)有發(fā)現(xiàn)我們同樣熟悉的@Value
和@Autowire
;
6.繼續(xù)跟著調(diào)用棧往下走,到AbstractAutowireCaptableBeanFactory
中,發(fā)現(xiàn)有一個(gè)循環(huán)去遍歷BeanPostProceccer
, 并過(guò)濾出InstantiationAwareBeanPostProcessor
,對(duì)創(chuàng)建中的Bean進(jìn)行處理,展開(kāi)BeanPostProceccer
的列表,會(huì)發(fā)現(xiàn)我們上邊看到的CommonAnnotationBeanPostProcessor
后邊還有個(gè)AutowiredAnnotationBeanPostProcessor
,此類也繼承自InstantiationAwareBeanPostProcessor
, 所以也會(huì)遍歷到,然后我們就會(huì)發(fā)現(xiàn)他與5中描述的邏輯類似,也是先找到需要注入的屬性,然后執(zhí)行注入。不同的是它解析@Value
和@Autowire
注解的屬性為需要注入的屬性;
7.上面提到的遍歷邏輯,是在對(duì)外層Bean進(jìn)行依賴注入,即外層Bean的初始化過(guò)程,因?yàn)橥鈱覤ean是@Service
注解修飾的,所以會(huì)在SpringBoot啟動(dòng)時(shí)掃描到進(jìn)行初始化
所以我們?cè)偻伦邲](méi)幾步就到了SpringApplication.run
4. 問(wèn)題出現(xiàn)邏輯梳理
- 應(yīng)用啟動(dòng),掃描
@Service
注解修飾的外層Bean,對(duì)其進(jìn)行初始化; - Bean的初始化由若干實(shí)現(xiàn)
InstantiationAwareBeanPostProcessor
接口的類在一個(gè)循環(huán)中依次對(duì)Bean進(jìn)行處理; - 循環(huán)中負(fù)責(zé)依賴注入的類
CommonAnnotationBeanPostProcesso
r發(fā)現(xiàn)屬性needInitBean
有@Resource
修飾,需要進(jìn)行注入,此時(shí)BeanFactory中沒(méi)有needInitBean
這個(gè)Bean,故對(duì)其進(jìn)行初始化,此時(shí)外層Bean的initValue
還沒(méi)有注入進(jìn)來(lái),所以內(nèi)層Bean初始化needInitValue
為null
; - 循環(huán)中負(fù)責(zé)依賴注入的類
AutowiredAnnotationBeanPostProcessor
發(fā)現(xiàn)屬性initValue
有@Value
修飾,需要進(jìn)行注入,執(zhí)行注入; - 完成外層Bean的創(chuàng)建;
5. 結(jié)論
通過(guò)上述追蹤,我們可以得出出現(xiàn)我們最初問(wèn)題的原因:由于@Value
和@Resource
在注入時(shí)并非用一個(gè)類進(jìn)行注入,存在先后關(guān)系
故雖然外層Bean已經(jīng)初始化一半去初始化內(nèi)層Bean,initValue
仍然沒(méi)有值。
另外退一步說(shuō),如果我們使用的是@Autowire
,而不是@Resource
,@Autowire
和@Value
是由同一個(gè)BeanPostProceccer
進(jìn)行注入的
是不是@Value寫(xiě)在前面,本程序就能通呢?
運(yùn)行了一下是可以的,然而這并不嚴(yán)謹(jǐn),因?yàn)榫退闶峭粋€(gè)BeanPostProceccer
進(jìn)行注入, 其屬性的注入順序是依賴反射包下的Class.getDeclaredFields
方法獲得的,而此方法注釋明確寫(xiě)道,返回的數(shù)組是無(wú)序的。
所以我們盡量還是避免這種寫(xiě)法,將@Bean
注解配置的Bean放在@Configuration
注解修飾的專門(mén)用于配置的類中較為穩(wěn)妥。
ps: 如果我們將initValue
使用屬性注入,而needInitBean
使用@Autowire
修飾setter
注入,可以保證嚴(yán)謹(jǐn),因?yàn)槟壳暗膶?shí)現(xiàn)都是先進(jìn)行屬性注入在進(jìn)行方法注入,不提倡。
到此這篇關(guān)于關(guān)于spring的@Bean注解放入引用Bean中初始化失敗分析的文章就介紹到這了,更多相關(guān)spring@Bean注解引用Bean內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)實(shí)例解析Python文件操作實(shí)現(xiàn)步驟
這篇文章主要介紹了通過(guò)實(shí)例解析Python文件操作實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Python利用腳本實(shí)現(xiàn)自動(dòng)發(fā)送電子郵件
這篇文章主要為大家詳細(xì)介紹了Python如何利用腳本實(shí)現(xiàn)自動(dòng)發(fā)送電子郵件功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-01-01基于Python實(shí)現(xiàn)迪杰斯特拉和弗洛伊德算法
這篇文章主要為大家詳細(xì)介紹了基于Python實(shí)現(xiàn)迪杰斯特拉和弗洛伊德算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Python分支語(yǔ)句與循環(huán)語(yǔ)句應(yīng)用實(shí)例分析
這篇文章主要介紹了Python分支語(yǔ)句與循環(huán)語(yǔ)句應(yīng)用,結(jié)合具體實(shí)例形式詳細(xì)分析了Python分支語(yǔ)句與循環(huán)語(yǔ)句各種常見(jiàn)應(yīng)用操作技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2019-05-05Python Django 通用視圖和錯(cuò)誤視圖的使用代碼
這篇文章主要介紹了Python Django 通用視圖和錯(cuò)誤視圖的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04python壓縮文件夾內(nèi)所有文件為zip文件的方法
這篇文章主要介紹了python壓縮文件夾內(nèi)所有文件為zip文件的方法,可實(shí)現(xiàn)簡(jiǎn)單的zip文件壓縮功能,需要的朋友可以參考下2015-06-06