前言
相信很多小伙伴在工作中都會遇到循環依賴,不過大多數它是這樣顯示的:
還會提示這么一句:
Requested bean is currently in creation: Is there an unresolvable circular reference?
老鐵!這就是發生循環依賴了!
當然這里是一個異常情況。
在我的一篇文章中介紹如何避免 Spring 自調用事務失效,其中網友給建議,說可以在類中注入自身,然后調用,而注入自身的過程也是循環依賴的處理過程。
下面就一起看一看,什么是循環依賴,以及 Spring 是如何解決循環依賴的?
什么是循環依賴
Circular dependencies
Dependency Resolution Process[1]
Spring IoC 容器會在運行時檢測到構造函數注入循環引用,并拋出
BeanCurrentlyInCreationException。所以要避免構造函數注入,可以使用 setter 注入替代。
根據官方文檔說明,Spring 會自動解決基于 setter 注入的循環依賴。
當然在咱們工作中現在都使用 @Autowired 注解來注入屬性。
PS: @Autowired 是通過反射進行賦值。
這里從我們最經常使用的場景切入,看 Spring 是如何解決循環依賴的?
代碼
@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 三個類,可以看到發生了循環依賴:
循環依賴
但是即使發生了循環依賴,我們依然可以啟動 OK,使用并沒有任何影響。
Spring 是如何解決循環依賴的
在 Spring 單例 Bean 的創建 中介紹介紹了使用三級緩存。
singletonObjects: 一級緩存,存儲單例對象,Bean 已經實例化,初始化完成。
earlySingletonObjects: 二級緩存,存儲 singletonObject,這個 Bean 實例化了,還沒有初始化。
singletonFactories: 三級緩存,存儲 singletonFactory。
當然,這里看著比較長,可以簡化一下:
通過 Debug 來說明生成過程
從 preInstantiateSingletons 方法開始:
添加斷點 beanName.equals("circularServiceA")
啟動Debug:
Start
會從緩存中獲取單例 Bean
這里很顯然獲取不到,繼續執行,創建單例實例
發現是單例再次獲取
這里還會從一級緩存獲取一次 circularServiceA , 沒有獲取到,將 circularServiceA 添加到在創建的池子里面 (
singletonsCurrentlyInCreation 是一個 set 集合)。
然后會調用工廠方法 createBean(beanName, mbd, args) 創建對象。
createBean 方法
在 createBean 中去實例化 Bean 。
判斷是否是循環引用,是的話需要添加到三級緩存中。
添加到三級緩存
circularServiceA 不在一級緩存中,則將 circularServiceA 的 singletonFactory 添加到 三級緩存 (singletonFactories) 中,同時從二級緩存中移除。
到這一步為止,circularServiceA 已經在三級緩存中了。
開始對 Bean 的屬性進行賦值。
屬性賦值
在 populateBean 方法中執行到
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrAppedInstance(), beanName);
就會對屬性進行賦值
屬性賦值
在 injet 方法中,回去解決相關依賴。
解決依賴
繼續 Debug ,發現解決依賴,最后發現其實又調用回 beanFactory.getBean(beanName);
不過這次創建的是 circularServiceB。
下面是調用鏈:
調用鏈
circularServiceB 的過程和 circularServiceA 的一樣,也是創建了三級緩存,然后去創建 circularServiceC
singletionFactories
這時候三級緩存里面有它們三個的 singletonFactory 。
circularServiceC 也調用到 doGetBean 方法去獲取 circularServiceA
不過這次 調用到 Object sharedInstance = getSingleton(beanName); 的時候, circularServiceA 已經存在了。
這次調用雖然沒有從一級緩存 (singletonObjects) 中獲取到 circularServiceA,但是 circularServiceA 在創建中,所以進入判斷
在這里執行完之后, circularServiceA 從三級緩存升級到二級緩存
使用反射對 circularServiceC 中的 circularServiceA 進行賦值, 此時 circularServiceA 是在 二級緩存中。
那就比較好奇了,這時候 circularServiceC 里面的 circularServiceA 已經通過反射賦值,這個賦值給的是什么值?
查看代碼:
這塊是從三級緩存(singletonFactories)中獲取的 singletonObject,然后調用
singletonObject = singletonFactory.getObject();
獲取的一個對象
這里獲取到的是 circularServiceA 的引用,注意 circularServiceA 這時候還沒創建完成,只是引用。所以這里賦值的是 circularServiceA 的引用。
到這里 circularServiceC 就創建完了。
然后會將 C 添加到一級緩存和已注冊列表中,同時從二級三級緩存中刪除 C。
繼續執行 B 和 A 的屬性賦值以及后續的初始化流程。
至此,循環依賴解決完畢。
總結
Spring 使用三級緩存來解決循環依賴的問題,三級緩存分別是:
- singletonObjects: 一級緩存,存儲單例對象,Bean 已經實例化,初始化完成。
- earlySingletonObjects: 二級緩存,存儲 singletonObject,這個 Bean 實例化了,還沒有初始化。
- singletonFactories: 三級緩存,存儲 singletonFactory。
本文也通過 Debug 來驗證了使用三級緩存解決依賴的過程。
不過還有一些問題沒有說明:
- 循環依賴和代理之間的關系是什么?比如 @Transactional 和 @Async 注解會對循環依賴產生什么影響?
- 為什么要用三級緩存?二級緩存不可以么?
相關推薦
- Spring 源碼學習 16:單例 Bean 創建
- Spring 源碼學習 15:finishBeanFactoryInitialization(重點)
- Spring 源碼學習 14:initApplicationEventMulticaster
引用鏈接:
[1]
Spring 官方文檔:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution