Spring 是如何解決循環依賴的?

前言

相信很多小夥伴在工作中都會遇到循環依賴,不過大多數它是這樣顯示的:

還會提示這麼一句:

Requested bean is currently in creation: Is there an unresolvable circular reference?

老鐵!這就是發生循環依賴了!

當然這裡是一個異常情況。

在我的一篇文章中介紹如何避免 Spring 自調用事務失效,其中網友給建議,說可以在類中注入自身,然後調用,而注入自身的過程也是循環依賴的處理過程。

下面就一起看一看,什麼是循環依賴,以及 Spring 是如何解決循環依賴的?

什麼是循環依賴

Circular dependencies

Dependency Resolution Process

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 來驗證了使用三級快取解決依賴的過程。

不過還有一些問題沒有說明:

  1. 循環依賴和代理之間的關係是什麼?比如 @Transactional 和 @Async 註解會對循環依賴產生什麼影響?
  2. 為什麼要用三級快取?二級快取不可以么?

相關推薦