前情回顧
一探
Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎中講到了循環依賴問題
同樣說明了Spring只能解決setter方式的循環依賴,不能解決構造方法的循環依賴
重點介紹了Spring是如何解決setter方式的循環依賴,感興趣的可以去看下
二探
既然Spring不能解決構造方法的循環依賴,那么它是如何甄別構造方法循環依賴的了?
所以進行了二探:再探循環依賴 → Spring 是如何判定原型循環依賴和構造方法循環依賴的?
從源碼的角度講述了Spring是如何判定構造方法循環依賴、原型循環依賴的
感興趣的可以去看下
大家跟源碼的時候,一定要注意版本!!!
項目模擬
自認為經過了前兩探,對Spring循環依賴的問題已了若指掌,可面對線上突如其來的循環依賴問題,樓主竟然沒能一眼看出來!!!
這樓主能忍?于是樓主又跟起了Spring源碼,看看問題到底出在哪?
SpringBoot版本是2.0.3.RELEASE
線上服務采用k8s部署,本地環境未采用k8s部署
本地啟動從未出現循環依賴問題,線上環境也只是偶發的pod啟動失敗(提示信息直指循環依賴)
問題偶發,而非必現,很是頭疼,但問題還是得解決,從提示信息著手唄
根據錯誤提示信息,樓主模擬出了一個簡化的工程,方便我們進行問題排查
非常簡單,完整地址:spring-other-circular-reference
我們來看下類圖
MyListener、MyService、MyManager很常規,特殊的是MyConfig和MySender
問題復現
如果按上述工程結構,本地很難復現問題 ,反正樓主是沒復現出來
我們稍做調整,將MySender前置,如下
啟動失敗,錯誤信息如下:
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?讓樓主感到了陌生
問題分析
我們從以下幾個方面來分析
BeanDefinition 掃描
目前XML方式的Bean定義越來越少,除了一些遺留的老項目,基本看不到XML方式的Bean定義了
所以我們只關注注解方式的Bean定義的掃描
文件夾的掃描順序與文件夾名字的升序一致,文件的順序與文件名的升序一致,如下所示
有興趣的可以去跟下ConfigurationClassParser類中doProcessConfigurationClass方法;樓主做了下簡單的總結
@ComponentScan的處理早于@Bean
BeanDefinition掃描過程中,會按掃描順序會往DefaultListableBeanFactory的beanDefinitionMap中添加BeanDefinition,往beanDefinitionNames添加BeanName
我們來跟下源碼,看是不是如上所說
先被掃描的BeanDefinition的BeanName會被先添加到beanDefinitionNames
BeanDefinition 覆蓋
MyConfig中通過@Bean定義了MySender,而MySender類上又用了@Component進行修飾
那創建MySender實例的時候到底調用的哪個構造方法?(有參還是無參?)
關于 Spring Boot 中創建對象的疑慮 → @Bean 與 @Component 同時作用同一個類,會怎么樣?從源碼的角度分析了這個問題
結論是:SpringBoot 2.0.3.RELEASE中,@Configuration + @Bean修飾的BeanDefinition會覆蓋掉@Component修飾的BeanDefinition
也就說MySender類上的@Component其實沒用,加不加效果是一樣的,這里說的沒用、效果僅僅指的是MySender的BeanDefinition
Bean 實例化順序
BeanDefinition用來構建實例,那么MySender上的@Component就有作用了,它決定了MySender的實例化順序
是先于MyConfig、MyListener、MyServiceImpl、MyManager實例化的
我們來看下Bean的實例化順序
理論上來講,先被掃描的Bean會先被實例化;Bean實例化的過程中會填充屬性,可能會導致后被掃描的Bean提前被實例化
如果Bean之間沒有依賴,那么會嚴格按照Bean的掃描順序實例化
再看問題
我們再回到前面的問題
這種情況下,我們分析下Is there an unresolvable circular reference?是如何產生的
相較于MyConfig、MyListener、MyManager、MyServiceImpl,MySender是最先被掃描到的,所以它最先被實例化
因為MyConfig中通過@Bean修飾了MySender的BeanDefinition
會覆蓋掉MySender自身的無參BeanDefinition
所以會通過MySender的有參構造方法來創建MySender實例
因為有參構造方法依賴myListener,所以去Spring容器中找MyListener實例,沒有找到則創建,然后填充MyListener實例的屬性
以此類推,實例的創建過程如下所示:
Is there an unresolvable circular reference?就此產生
相當于是變種的構造方法循環依賴
最初狀態
我們還原MySender位置
此時最先實例化的是MyConfig,實例化過程如下
對象是都可以正常實例化、初始化的
這種情況理論上來講是不會出現Is there an unresolvable circular reference?
線上問題
一通分析下來,還是沒能找到線上Is there an unresolvable circular reference?的原因
很是尷尬,但是我萌生了這樣的想法:是不是在k8s部署過程中,BeanDefinition的掃描會有偶發的隨機性?
問題修復
雖然我們沒能找到線上問題的確切原因,但還是有辦法去根治這個問題的
Spring不能處理構造方法循環依賴,那我們就去規避它
刪掉MyConfig,MySender改成
或MySender改成
還有@PostConstruct等,方式有很多,只要不產生構造方法循環依賴就好
總結
1、BeanDefinition掃描順序
如果我們去跟源代碼就會發現,以啟動類為起點,掃描啟動類同級目錄下的所有文件夾
按文件夾名升序順序進行掃描,會遞歸掃描每個文件夾
文件掃描也是按文件名升序順序進行
從線上問題來看,對這個掃描順序,樓主是持懷疑態度的:是Spring會偶發的隨機掃描,還是pod會導致偶發的隨機掃描
2、BeanDefinition覆蓋
只要我們讀了源碼,了解Spring對各個注解的掃描順序,就清楚它們的替換關系了
BeanDefinition覆蓋并不會影響BeanDefinition的掃描順序
也就是不會改變BeanName在beanDefinitionNames中的位置,即不會影響Bean的示例化順序
3、Bean實例化順序
理論上來講,先被掃描到的就先被實例化,但實例化過程中的屬性填充會打亂這個順序,會將被依賴的對象提前實例化
4、Spring版本
?一定要結合版本來看問題
版本不同,底層實現可能會不同
-
源碼
+關注
關注
8文章
670瀏覽量
30255 -
循環
+關注
關注
0文章
92瀏覽量
16290 -
spring
+關注
關注
0文章
340瀏覽量
14992
原文標題:記一次線上偶現的Spring循環依賴問題
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
java spring教程
什么是java spring
Spring工作原理
「Spring認證」什么是Spring GraphQL?

Spring開發過程中依賴注入的幾個知識點
Spring Validation的使用
從源碼層面深度剖析Spring循環依賴
怎樣使用Kiuwan保護Spring Boot應用程序呢?
Spring Boot的啟動原理

Spring Boot 的設計目標

評論