Spring循環依賴原理,如何解決?

什麼是循環依賴?

從字面上來理解就是A依賴B的同時B也依賴了A,就像上面這樣,或者C依賴與自己本身。體現到代碼層次就是這個樣子

@Component
public class A {
 // A中注入了B
 @Autowired
 private B b;
}
---
@Component
public class A {
 // A中注入了B
 @Autowired
 private B b;
}
---
// 自己依賴自己
@Component
public class C {
 // C中注入了C
 @Autowired
 private C c;
}

雖然體現形式不一樣,但是實際上都是循環依賴的問題。

什麼情況下循環依賴可以被處理?

Spring解決循環依賴是有前置條件

  • 出現循環依賴的Bean必須要是單例(singleton),如果依賴prototype則完全不會有此需求。

  • 依賴注入的方式不能全是構造器注入的方式(只能解決setter方法的循環依賴,這是錯誤的)

1. AB 均採用setter方法注入 結果OK

2. AB 均採用屬性Autowired注入 結果ok

3. AB均採用構造器方法注入 出現循環依賴

4. A中注入B的方式爲setter方法,B中注入A的方式爲構造器

5. A中注入B的方式爲構造器,B中注入A的方式爲setter方法。

結論

依賴情況 依賴注入方式 是否解決
AB相互依賴(循環依賴) 均採用setter方法注入
AB相互依賴(循環依賴) 均採用屬性自動注入
AB相互依賴(循環依賴) 均採用構造器注入
AB相互依賴(循環依賴) A中注入B的方式爲setter方法,B中注入A的方式爲構造器
AB相互依賴(循環依賴) B中注入A的方式爲setter方法,A中注入B的方式爲構造器,Spring在創建Bean時默認會根據自然排序進行創建,A會先於B進行創建

從上面的測試結果我們可以看到,不是隻有在setter方法注入的情況下循環依賴才能被解決,即使存在構造器注入的場景下,循環依賴依然被可以被正常處理掉

Spring循環依賴的通俗說

Spring bean 的創建,其本質上還是一個對象的創建,既然是對象,一定要明白一點就是,一個完整的對象包含兩部分:當前對象實例化和對象屬性的實例化。在Spring中,對象的實例化是通過反射實現的,而對象的屬性則是在對象實例化之後通過一定的方式設置的。這個過程可以按照如下方式進行理解:

大致繪製依賴流程圖如下:

圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示我們要嘗試獲取的目標對象。圖中的黑色箭頭表示一開始的方法調用走向,走到最後,返回了Spring中緩存的A對象之後,表示遞歸調用返回了,此時使用綠色箭頭表示。從圖中我們可以很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,因爲其屬性已經設置完成了。

到這裏,Spring整個解決循環依賴問題的實現思路已經比較清楚了。對於整體過程只要理解兩點:

  • Spring是通過遞歸的方式獲取目標bean及其所依賴的bean的;

  • Spring實例化一個bean的時候,是分兩步進行的,首先實例化目標bean,然後爲其注入屬性。

結合這兩點,也就是說,Spring在實例化一個bean的時候,是首先遞歸的實例化其所依賴的所有bean,直到某個bean沒有依賴其他bean,此時就會將該實例返回,然後反遞歸的將獲取到的bean設置爲各個上層bean的屬性的

Spring循環依賴進階

一個對象一般創建過程有3部分組成:

  1. 實例化:簡單理解就是new了一個對象

  2. 屬性注入:爲實例化中new出來的對象填充屬性

  3. 初始化:執行aware接口中的方法,初始化方法,完成AOP代理

Spring是通過「三級緩存」來解決上述問題的:

  • singletonObjects:一級緩存 存儲的是所有創建好了的單例Bean

  • earlySingletonObjects:完成實例化,但是還未進行屬性注入及初始化的對象

  • singletonFactories:提前暴露的一個單例工廠,二級緩存中存儲的就是從這個工廠中獲取到的對象

然後接下來說下普通循環依賴跟帶AOP的循環依賴。

普通循環依賴圖

結論:沒有進行AOP的Bean間的循環依賴 從上圖分析可以看出,這種情況下「三級緩存根本沒用」!所以不會存在什麼提高了效率的說法

帶AOP循環依賴

帶AOP的跟不帶AOP的其實幾乎一樣,只是在三級緩存中存放的是函數式接口,在需要調用時直接返回代理對象。三級緩存存在的意義:

只有真正發生循環依賴的時候,纔去提前生成代理對象,否則只會創建一個工廠並將其放入到三級緩存中,但是不會去通過這個工廠去真正創建對象


是否可以用二級緩存而不用三級緩存?

答案:不可以,違背Spring在結合AOP跟Bean的生命週期的設計!Spring結合AOP跟Bean的生命週期(看下圖)本身就是通過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來完成的,在這個後置處理的postProcessAfterInitialization方法中對初始化後的Bean完成AOP代理。如果出現了循環依賴,那沒有辦法,只有給Bean先創建代理,但是沒有出現循環依賴的情況下,設計之初就是讓Bean在生命週期的「最後一步完成代理而不是在實例化後就立馬完成代理」

使用了三級緩存的情況下,A、B的創建流程

不使用三級緩存,直接在二級緩存中

結論:上面兩個流程的唯一區別在於爲A對象創建代理的時機不同,使用三級緩存的情況下爲A創建代理的時機是在B中需要注入A的時候,而不使用三級緩存的話在A實例化後就需要馬上爲A創建代理然後放入到二級緩存中去。三級緩存是無法提速的

回答模板

Spring如何解決循環依賴的

答:Spring通過三級緩存解決了循環依賴,其中一級緩存爲單例池(singletonObjects),二級緩存爲早期曝光對象earlySingletonObjects,三級緩存爲早期曝光對象工廠(singletonFactories)。

當A、B兩個類發生循環引用時,在A完成實例化後,就使用實例化後的對象去創建一個對象工廠,添加到三級緩存中,如果A被AOP代理,那麼通過這個工廠獲取到的就是A代理後的對象,如果A沒有被AOP代理,那麼這個工廠獲取到的就是A實例化的對象。

當A進行屬性注入時,會去創建B,同時B又依賴了A,所以創建B的同時又會去調用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取:

第一步:先獲取到三級緩存中的工廠;

第二步:調用對象工工廠的getObject方法來獲取到對應的對象,得到這個對象後將其注入到B中。緊接着B會走完它的生命週期流程,包括初始化、後置處理器等。

第三步:當B創建完後,會將B再注入到A中,此時A再完成它的整個生命週期。至此,循環依賴結束!

面試官:爲什麼要使用三級緩存呢?二級緩存能解決循環依賴嗎?

答:如果要使用二級緩存解決循環依賴,意味着所有Bean在實例化後就要完成AOP代理,這樣違背了Spring設計的原則,Spring在設計之初就是通過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來在Bean生命週期的最後一步來完成AOP代理,而不是在實例化後就立馬進行AOP代理。

跟蹤核心大致流程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章