前情回顧
一探
Spring 的循環(huán)依賴,源碼詳細(xì)分析 → 真的非要三級(jí)緩存嗎中講到了循環(huán)依賴問(wèn)題
同樣說(shuō)明了Spring只能解決setter方式的循環(huán)依賴,不能解決構(gòu)造方法的循環(huán)依賴
重點(diǎn)介紹了Spring是如何解決setter方式的循環(huán)依賴,感興趣的可以去看下
二探
既然Spring不能解決構(gòu)造方法的循環(huán)依賴,那么它是如何甄別構(gòu)造方法循環(huán)依賴的了?
所以進(jìn)行了二探:再探循環(huán)依賴 → Spring 是如何判定原型循環(huán)依賴和構(gòu)造方法循環(huán)依賴的?
從源碼的角度講述了Spring是如何判定構(gòu)造方法循環(huán)依賴、原型循環(huán)依賴的
感興趣的可以去看下
大家跟源碼的時(shí)候,一定要注意版本!!!
項(xiàng)目模擬
自認(rèn)為經(jīng)過(guò)了前兩探,對(duì)Spring循環(huán)依賴的問(wèn)題已了若指掌,可面對(duì)線上突如其來(lái)的循環(huán)依賴問(wèn)題,樓主竟然沒(méi)能一眼看出來(lái)!!!
這樓主能忍?于是樓主又跟起了Spring源碼,看看問(wèn)題到底出在哪?
SpringBoot版本是2.0.3.RELEASE
線上服務(wù)采用k8s部署,本地環(huán)境未采用k8s部署
本地啟動(dòng)從未出現(xiàn)循環(huán)依賴問(wèn)題,線上環(huán)境也只是偶發(fā)的pod啟動(dòng)失敗(提示信息直指循環(huán)依賴)
問(wèn)題偶發(fā),而非必現(xiàn),很是頭疼,但問(wèn)題還是得解決,從提示信息著手唄
根據(jù)錯(cuò)誤提示信息,樓主模擬出了一個(gè)簡(jiǎn)化的工程,方便我們進(jìn)行問(wèn)題排查
非常簡(jiǎn)單,完整地址:spring-other-circular-reference
我們來(lái)看下類圖
MyListener、MyService、MyManager很常規(guī),特殊的是MyConfig和MySender
問(wèn)題復(fù)現(xiàn)
如果按上述工程結(jié)構(gòu),本地很難復(fù)現(xiàn)問(wèn)題 ,反正樓主是沒(méi)復(fù)現(xiàn)出來(lái)
我們稍做調(diào)整,將MySender前置,如下
啟動(dòng)失敗,錯(cuò)誤信息如下:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myConfig': Unsatisfied dependency expressed through field 'myListener'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myListener': Unsatisfied dependency expressed through field 'myService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myServiceImpl': Unsatisfied dependency expressed through field 'myManager'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myManager': Unsatisfied dependency expressed through field 'mySender'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mySender': Requested bean is currently in creation: Is there an unresolvable circular reference?
此刻的Is there an unresolvable circular reference?讓樓主感到了陌生
問(wèn)題分析
我們從以下幾個(gè)方面來(lái)分析
BeanDefinition 掃描
目前XML方式的Bean定義越來(lái)越少,除了一些遺留的老項(xiàng)目,基本看不到XML方式的Bean定義了
所以我們只關(guān)注注解方式的Bean定義的掃描
文件夾的掃描順序與文件夾名字的升序一致,文件的順序與文件名的升序一致,如下所示
有興趣的可以去跟下ConfigurationClassParser類中doProcessConfigurationClass方法;樓主做了下簡(jiǎn)單的總結(jié)
@ComponentScan的處理早于@Bean
BeanDefinition掃描過(guò)程中,會(huì)按掃描順序會(huì)往DefaultListableBeanFactory的beanDefinitionMap中添加BeanDefinition,往beanDefinitionNames添加BeanName
我們來(lái)跟下源碼,看是不是如上所說(shuō)
先被掃描的BeanDefinition的BeanName會(huì)被先添加到beanDefinitionNames
BeanDefinition 覆蓋
MyConfig中通過(guò)@Bean定義了MySender,而MySender類上又用了@Component進(jìn)行修飾
那創(chuàng)建MySender實(shí)例的時(shí)候到底調(diào)用的哪個(gè)構(gòu)造方法?(有參還是無(wú)參?)
關(guān)于 Spring Boot 中創(chuàng)建對(duì)象的疑慮 → @Bean 與 @Component 同時(shí)作用同一個(gè)類,會(huì)怎么樣?從源碼的角度分析了這個(gè)問(wèn)題
結(jié)論是:SpringBoot 2.0.3.RELEASE中,@Configuration + @Bean修飾的BeanDefinition會(huì)覆蓋掉@Component修飾的BeanDefinition
也就說(shuō)MySender類上的@Component其實(shí)沒(méi)用,加不加效果是一樣的,這里說(shuō)的沒(méi)用、效果僅僅指的是MySender的BeanDefinition
Bean 實(shí)例化順序
BeanDefinition用來(lái)構(gòu)建實(shí)例,那么MySender上的@Component就有作用了,它決定了MySender的實(shí)例化順序
是先于MyConfig、MyListener、MyServiceImpl、MyManager實(shí)例化的
我們來(lái)看下Bean的實(shí)例化順序
理論上來(lái)講,先被掃描的Bean會(huì)先被實(shí)例化;Bean實(shí)例化的過(guò)程中會(huì)填充屬性,可能會(huì)導(dǎo)致后被掃描的Bean提前被實(shí)例化
如果Bean之間沒(méi)有依賴,那么會(huì)嚴(yán)格按照Bean的掃描順序?qū)嵗?/p>
再看問(wèn)題
我們?cè)倩氐角懊娴膯?wèn)題
這種情況下,我們分析下Is there an unresolvable circular reference?是如何產(chǎn)生的
相較于MyConfig、MyListener、MyManager、MyServiceImpl,MySender是最先被掃描到的,所以它最先被實(shí)例化
因?yàn)?span style="margin-top:5px;margin-bottom:5px;padding:3px;background-color:rgb(245,245,245);border-width:1px;border-style:solid;border-color:rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;">MyConfig中通過(guò)@Bean修飾了MySender的BeanDefinition
會(huì)覆蓋掉MySender自身的無(wú)參BeanDefinition
所以會(huì)通過(guò)MySender的有參構(gòu)造方法來(lái)創(chuàng)建MySender實(shí)例
因?yàn)橛袇?gòu)造方法依賴myListener,所以去Spring容器中找MyListener實(shí)例,沒(méi)有找到則創(chuàng)建,然后填充MyListener實(shí)例的屬性
以此類推,實(shí)例的創(chuàng)建過(guò)程如下所示:
Is there an unresolvable circular reference?就此產(chǎn)生
相當(dāng)于是變種的構(gòu)造方法循環(huán)依賴
最初狀態(tài)
我們還原MySender位置
此時(shí)最先實(shí)例化的是MyConfig,實(shí)例化過(guò)程如下
對(duì)象是都可以正常實(shí)例化、初始化的
這種情況理論上來(lái)講是不會(huì)出現(xiàn)Is there an unresolvable circular reference?
線上問(wèn)題
一通分析下來(lái),還是沒(méi)能找到線上Is there an unresolvable circular reference?的原因
很是尷尬,但是我萌生了這樣的想法:是不是在k8s部署過(guò)程中,BeanDefinition的掃描會(huì)有偶發(fā)的隨機(jī)性?
問(wèn)題修復(fù)
雖然我們沒(méi)能找到線上問(wèn)題的確切原因,但還是有辦法去根治這個(gè)問(wèn)題的
Spring不能處理構(gòu)造方法循環(huán)依賴,那我們就去規(guī)避它
刪掉MyConfig,MySender改成
或MySender改成
還有@PostConstruct等,方式有很多,只要不產(chǎn)生構(gòu)造方法循環(huán)依賴就好
總結(jié)
1、BeanDefinition掃描順序
如果我們?nèi)ジ创a就會(huì)發(fā)現(xiàn),以啟動(dòng)類為起點(diǎn),掃描啟動(dòng)類同級(jí)目錄下的所有文件夾
按文件夾名升序順序進(jìn)行掃描,會(huì)遞歸掃描每個(gè)文件夾
文件掃描也是按文件名升序順序進(jìn)行
從線上問(wèn)題來(lái)看,對(duì)這個(gè)掃描順序,樓主是持懷疑態(tài)度的:是Spring會(huì)偶發(fā)的隨機(jī)掃描,還是pod會(huì)導(dǎo)致偶發(fā)的隨機(jī)掃描
2、BeanDefinition覆蓋
只要我們讀了源碼,了解Spring對(duì)各個(gè)注解的掃描順序,就清楚它們的替換關(guān)系了
BeanDefinition覆蓋并不會(huì)影響BeanDefinition的掃描順序
也就是不會(huì)改變BeanName在beanDefinitionNames中的位置,即不會(huì)影響Bean的示例化順序
3、Bean實(shí)例化順序
理論上來(lái)講,先被掃描到的就先被實(shí)例化,但實(shí)例化過(guò)程中的屬性填充會(huì)打亂這個(gè)順序,會(huì)將被依賴的對(duì)象提前實(shí)例化
4、Spring版本
?一定要結(jié)合版本來(lái)看問(wèn)題
版本不同,底層實(shí)現(xiàn)可能會(huì)不同
-
源碼
+關(guān)注
關(guān)注
8文章
641瀏覽量
29213 -
循環(huán)
+關(guān)注
關(guān)注
0文章
92瀏覽量
15974 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14343
原文標(biāo)題:記一次線上偶現(xiàn)的Spring循環(huán)依賴問(wèn)題
文章出處:【微信號(hào):AndroidPush,微信公眾號(hào):Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論