1. 程式人生 > >爛大街的 Spring 迴圈依賴問題,你覺得自己會了嗎

爛大街的 Spring 迴圈依賴問題,你覺得自己會了嗎

> 文章已收錄在 GitHub [JavaKeeper](https://github.com/Jstarfish/JavaKeeper) ,N 線網際網路開發、面試必備技能兵器譜,筆記自取。 > > 微信搜「 **JavaKeeper** 」程式設計師成長充電站,網際網路技術武道場。無套路領取 500+ 本電子書和 30+ 視訊教學和原始碼。 ## 前言 迴圈依賴問題,算是一道爛大街的面試題了,解毒之前,我們先來回顧兩個知識點: 初學 Spring 的時候,我們就知道 IOC,控制反轉麼,它將原本在程式中手動建立物件的控制權,交由 Spring 框架來管理,不需要我們手動去各種 `new XXX`。 儘管是 Spring 管理,不也得建立物件嗎, Java 物件的建立步驟很多,可以 `new XXX`、序列化、`clone()` 等等, 只是 Spring 是通過反射 + 工廠的方式建立物件並放在容器的,建立好的物件我們一般還會對物件屬性進行賦值,才去使用,可以理解是分了兩個步驟。 好了,對這兩個步驟有個印象就行,接著我們進入迴圈依賴,先說下迴圈依賴的概念 ### 什麼是迴圈依賴 所謂的迴圈依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了迴圈依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A,形成了迴圈依賴。更或者是自己依賴自己。它們之間的依賴關係如下: ![](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20200831102205.png) 這裡以兩個類直接相互依賴為例,他們的實現程式碼可能如下: ```java public class BeanB { private BeanA beanA; public void setBeanA(BeanA beanA) { this.beanA = beanA; } } public class BeanA { private BeanB beanB; public void setBeanB(BeanB beanB) { this.beanB = beanB; } } ``` 配置資訊如下(用註解方式注入同理,只是為了方便理解,用了配置檔案): ```java ``` Spring 啟動後,讀取如上的配置檔案,會按順序先例項化 A,但是建立的時候又發現它依賴了 B,接著就去例項化 B ,同樣又發現它依賴了 A ,這尼瑪咋整?無限迴圈呀 Spring “肯定”不會讓這種事情發生的,如前言我們說的 Spring 例項化物件分兩步,第一步會先建立一個原始物件,只是沒有設定屬性,可以理解為"半成品"—— 官方叫 A 物件的早期引用(EarlyBeanReference),所以當例項化 B 的時候發現依賴了 A, B 就會把這個“半成品”設定進去先完成例項化,既然 B 完成了例項化,所以 A 就可以獲得 B 的引用,也完成例項化了,這其實就是 Spring 解決迴圈依賴的思想。 ![有點懵逼](https://i04piccdn.sogoucdn.com/7332d8fe139e38e4) 不理解沒關係,先有個大概的印象,然後我們從原始碼來看下 Spring 具體是怎麼解決的。 ## 原始碼解毒 > 程式碼版本:5.0.16.RELEASE 在 Spring IOC 容器讀取 Bean 配置建立 Bean 例項之前, 必須對它進行例項化。只有在容器例項化後,才可以從 IOC 容器裡獲取 Bean 例項並使用,迴圈依賴問題也就是發生在例項化 Bean 的過程中的,所以我們先回顧下獲取 Bean 的過程。 ### 獲取 Bean 流程 Spring IOC 容器中獲取 bean 例項的簡化版流程如下(排除了各種包裝和檢查的過程) ![](https://cdn.jsdelivr.net/gh/Jstarfish/picBed/img/20200901094342.png) 大概的流程順序(可以結合著原始碼看下,我就不貼了,貼太多的話,嘔~嘔嘔,想吐): 1. 流程從 `getBean` 方法開始,`getBean` 是個空殼方法,所有邏輯直接到 `doGetBean` 方法中 2. `transformedBeanName` 將 name 轉換為真正的 beanName(name 可能是 FactoryBean 以 & 字元開頭或者有別名的情況,所以需要轉化下) 3. 然後通過 `getSingleton(beanName)` 方法嘗試從快取中查詢是不是有該例項 sharedInstance(單例在 Spring 的同一容器只會被建立一次,後續再獲取 bean,就直接從快取獲取即可) 4. 如果有的話,sharedInstance 可能是完全例項化好的 bean,也可能是一個原始的 bean,所以再經 `getObjectForBeanInstance` 處理即可返回 5. 當然 sharedInstance 也可能是 null,這時候就會執行建立 bean 的邏輯,將結果返回 第三步的時候我們提到了一個快取的概念,這個就是 Spring 為了解決單例的迴圈依賴問題而設計的 **三級快取** ```java /** Cache of singleton objects: bean name --> bean instance */ private f