Spring 是如何解決迴圈依賴的?
阿新 • • 發佈:2021-01-26
### 前言
相信很多小夥伴在工作中都會遇到迴圈依賴,不過大多數它是這樣顯示的:
![](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