1. 程式人生 > >Spring 是如何解決迴圈依賴的?

Spring 是如何解決迴圈依賴的?

### 前言 相信很多小夥伴在工作中都會遇到迴圈依賴,不過大多數它是這樣顯示的: ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/F0EjJ0-cjS89L.png) 還會提示這麼一句: *Requested bean is currently in creation: Is there an unresolvable circular reference?* 老鐵!這就是發生迴圈依賴了! 當然這裡是一個異常情況。 在我的一篇文章中介紹如何避免 [Spring 自呼叫事務失效](https://mp.weixin.qq.com/s/Hn3C5a_hNPcscB93XlFO8Q),其中網友給建議,說可以在類中注入自身,然後呼叫,而注入自身的過程也是迴圈依賴的處理過程。 下面就一起看一看,什麼是迴圈依賴,以及 Spring 是如何解決迴圈依賴的? ### 什麼是迴圈依賴 ![Circular dependencies](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/vinf1u-ALdO65.png) [Dependency Resolution Process](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution "Spring 官方文件") > Spring IoC 容器會在執行時檢測到**建構函式注入**迴圈引用,並丟擲 BeanCurrentlyInCreationException。 > > 所以要避免建構函式注入,可以使用 setter 注入替代。 根據官方文件說明,Spring 會自動解決基於 setter 注入的迴圈依賴。 當然在咱們工作中現在都使用 `@Autowired` 註解來注入屬性。 > PS: @Autowired 是通過反射進行賦值。 這裡從我們最經常使用的場景切入,看 Spring 是如何解決迴圈依賴的? #### 程式碼 ```java @Service public class CircularServiceA { @Autowired private CircularServiceB circularServiceB; } @Service public class CircularServiceB { @Autowired private CircularServiceC circularServiceC; } @Service public class CircularServiceC { @Autowired private CircularServiceA circularServiceA; } ``` 這裡有 A、B、C 三個類,可以看到發生了迴圈依賴: ![迴圈依賴](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/2G8CEa-rstD88.png) 但是即使發生了迴圈依賴,我們依然可以啟動 OK,使用並沒有任何影響。 ### Spring 是如何解決迴圈依賴的 在 [Spring 單例 Bean 的建立](https://mp.weixin.qq.com/s/qZ4xXlqpNzsdHkvFm02Yuw) 中介紹介紹了使用三級快取。 >singletonObjects: 一級快取,儲存單例物件,Bean 已經例項化,初始化完成。 > >earlySingletonObjects: 二級快取,儲存 singletonObject,這個 Bean 例項化了,還沒有初始化。 > >singletonFactories: 三級快取,儲存 singletonFactory。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/KS60bs-aWqhoU.png) 當然,這裡看著比較長,可以簡化一下: ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/1QnEIw-1agsnx.png) ### 通過 Debug 來說明生成過程 從 preInstantiateSingletons 方法開始: 新增斷點 `beanName.equals("circularServiceA")` 啟動Debug: ![Start](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/SYoDdM-0pgCsu.png) 會從快取中獲取單例 Bean ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/AeMsAm-KnFiZA.png) 這裡很顯然獲取不到,繼續執行,建立單例例項 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/JRFOxl-Tsbi9H.png) 發現是單例再次獲取 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/YWcgWk-90K6tS.png) 這裡還會從一級快取獲取一次 `circularServiceA` , 沒有獲取到,將 `circularServiceA` 新增到在建立的池子裡面 (singletonsCurrentlyInCreation 是一個 set 集合)。 然後會呼叫工廠方法 createBean(beanName, mbd, args) 建立物件。 ![createBean 方法](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/MnA2Db-juxIN7.png) 在 createBean 中去例項化 Bean 。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/kVo7G6-DAtYGM.png) 判斷是否是迴圈引用,是的話需要新增到三級快取中。 ![新增到三級快取](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/iRDZbq-4fZA5S.png) `circularServiceA` 不在一級快取中,則將 `circularServiceA` 的 singletonFactory 新增到 三級快取 (singletonFactories) 中,同時從二級快取中移除。 到這一步為止,circularServiceA 已經在三級快取中了。 開始對 Bean 的屬性進行賦值。 ![屬性賦值](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/oZ70es-2WsKdT.png) 在 populateBean 方法中執行到 `PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);` 就會對屬性進行賦值 ![屬性賦值](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ZjKmVC-J3xIl5.png) 在 injet 方法中,回去解決相關依賴。 ![解決依賴](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/EnkUit-T1uQvh.png) 繼續 Debug ,發現解決依賴,最後發現其實又呼叫回 `beanFactory.getBean(beanName);` 不過這次建立的是 `circularServiceB`。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/WuKewa-ACO0Ae.png) 下面是呼叫鏈: ![呼叫鏈](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ykN9uD-2XrZUz.png) `circularServiceB` 的過程和 `circularServiceA` 的一樣,也是建立了三級快取,然後去建立 `circularServiceC` ![singletionFactories](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/VdfOIS-SUo0Fz.png) 這時候三級快取裡面有它們三個的 singletonFactory 。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/6px7bF-uBnMnF.png) `circularServiceC` 也呼叫到 doGetBean 方法去獲取 `circularServiceA` 不過這次 呼叫到 `Object sharedInstance = getSingleton(beanName);` 的時候, `circularServiceA` 已經存在了。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/EHvsaz-wEqbsG.png) 這次呼叫雖然沒有從一級快取 (singletonObjects) 中獲取到 circularServiceA,但是 `circularServiceA` 在**建立中**,所以進入判斷 在這裡執行完之後, `circularServiceA` 從三級快取升級到二級快取 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/wXvq02-Ayomqs.png) 使用反射對 `circularServiceC` 中的 `circularServiceA` 進行賦值, 此時 `circularServiceA` 是在 二級快取中。 **那就比較好奇了,這時候 circularServiceC 裡面的 circularServiceA 已經通過反射賦值,這個賦值給的是什麼值?** 檢視程式碼: ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/WdGk05-KZ94hA.png) 這塊是從三級快取(singletonFactories)中獲取的 singletonObject,然後呼叫 `singletonObject = singletonFactory.getObject();` 獲取的一個物件 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ZlcQGZ-tKDGNM.png) 這裡獲取到的是 circularServiceA 的引用,注意 circularServiceA 這時候還沒建立完成,只是引用。所以這裡賦值的是 circularServiceA 的引用。 到這裡 `circularServiceC` 就建立完了。 然後會將 C 新增到一級快取和已註冊列表中,同時從二級三級快取中刪除 C。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/fcNkmJ-fth6DQ.png) ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/QwK4yN-dbrlQj.png) 繼續執行 B 和 A 的屬性賦值以及後續的初始化流程。 至此,迴圈依賴解決完畢。 ### 總結 Spring 使用三級快取來解決迴圈依賴的問題,三級快取分別是: - **singletonObjects:** 一級快取,儲存單例物件,Bean 已經例項化,初始化完成。 - **earlySingletonObjects:** 二級快取,儲存 singletonObject,這個 Bean 例項化了,還沒有初始化。 - **singletonFactories:** 三級快取,儲存 singletonFactory。 本文也通過 Debug 來驗證了使用三級快取解決依賴的過程。 ![](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/1QnEIw-1agsnx.png) 不過還有一些問題沒有說明: 1. 迴圈依賴和代理之間的關係是什麼?比如 @Transactional 和 @Async 註解會對迴圈依賴產生什麼影響? 2. 為什麼要用三級快取?二級快取不可以麼? #### 相關推薦 - [Spring 原始碼學習 16:單例 Bean 建立](https://mp.weixin.qq.com/s/qZ4xXlqpNzsdHkvFm02Yuw) - [Spring 原始碼學習 15:finishBeanFactoryInitialization(重點)](https://mp.weixin.qq.com/s/MAlT1Y5MVmEclAZC6rgojQ) - [Spring 原始碼學習 14:initApplicationEventMulticaster](https://mp.weixin.qq.com/s/bKmqVFuLLLCquWf