Spring 是如何解決循環依賴的?
前言
相信很多小夥伴在工作中都會遇到循環依賴,不過大多數它是這樣顯示的:
還會提示這麼一句:
Requested bean is currently in creation: Is there an unresolvable circular reference?
老鐵!這就是發生循環依賴了!
當然這裡是一個異常情況。
在我的一篇文章中介紹如何避免 Spring 自調用事務失效,其中網友給建議,說可以在類中注入自身,然後調用,而注入自身的過程也是循環依賴的處理過程。
下面就一起看一看,什麼是循環依賴,以及 Spring 是如何解決循環依賴的?
什麼是循環依賴
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:
會從快取中獲取單例 Bean
這裡很顯然獲取不到,繼續執行,創建單例實例
發現是單例再次獲取
這裡還會從一級快取獲取一次 circularServiceA
, 沒有獲取到,將 circularServiceA
添加到在創建的池子裡面 (singletonsCurrentlyInCreation 是一個 set 集合)。
然後會調用工廠方法 createBean(beanName, mbd, args) 創建對象。
在 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
這時候三級快取裡面有它們三個的 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