天天看點

Spring循環依賴還能這麼了解 (實在高)

作者:文字代表述說

在日落大道浪漫出逃 除了風沒有人知道

前言

哈喽大家好,我是一條

最近 有粉絲提到了 循環依賴 問題,以後再有人問你,拿這篇“吊打”他。

概念

什麼是循環依賴?

多個bean之間互相依賴,形成了一個閉環。比如:A依賴于B、B依賴于C、C依賴于A。

Spring循環依賴還能這麼了解 (實在高)

通常來說,如果問Spring容器内部如何解決循環依賴,一定是指預設的單例Bean中,基于 set 方法構造注入的屬性互相引用的場景。

循環依賴的種類及能否解決如下:

名稱 是否可解決循環依賴
構造器循環依賴
Setter循環依賴
Prototype作用域的循環依賴

報錯資訊

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘myDao’: Requested bean is currently in creation: Is there an unresolvable circular reference?           

翻譯一下

通過構造函數參數 0 表示的依賴關系未得到滿足;嵌套的異常是 建立名稱為'myDao'的bean時出錯。請求的Bean目前正在建立中。是否存在一個無法解決的循環引用?           

異常資訊:bean目前建立異常 org.springframework.beans.factory.BeanCurrentlyInCreationException。

通俗版了解

兩人拿槍對峙

現在甲乙兩個人,互相拿槍對峙,甲說乙先放,乙說甲先放。就是不開槍。

哎,就是玩!

相信這個場景大家在電視劇裡都見過吧,最後一般是“反派死于話多”。

但是回到我們 spring 裡,我們是不希望有人死亡的,也就是必須兩個 bean 都建立出來,怎麼辦?

必須有一人妥協

解決方案就是:必須有一個人先妥協。

甲說:我退一步,我先把彈夾卸了,你把槍放下。

乙一聽就感動了,滿含熱淚的拿槍放下了。

甲一看乙沒有打自己,也熱淚盈眶,兩人緊緊相擁。

從此過上了幸福美滿的生活……

Spring版了解

回到我們 spring 裡,先回顧一下 bean 的生命周期:

  • 執行個體化
  • 屬性指派
  • 初始化
  • 銷毀

簡單了解一下的上面的過程

執行個體化和初始化什麼差別?

是不是隻差了中間指派的過程,那隻執行個體化的 bean 可以使用嗎?

當然不可以!

也就是說隻執行個體化的bean是一個半成品,初始化之後才是成品,才可以使用。

現在A依賴B,B依賴A。

A對B說:我要完整的你
b也對a:我要完整的你
           

ok,兩人打起來了,拿槍對峙。怎麼解決?是不是得一個人妥協。

a說:算了吧,你給我個你的半成品,我将就一下。

b心裡尋思,他用我的半成品建立一個完整的a,然後我就可以建立了。

心裡這麼想,嘴上就爽快答應着:行,沒問題。

如此,a建立了完整的自己,b拿着a也完成了建立。

問題解決。

真的解決了嗎?成品和半成品都存在哪裡呢?

這就不得不提到大名鼎鼎的三級緩存。

三級緩存

spring提供了三級緩存來存放成品和半成品及工廠。位于 DefaultSingletonBeanRegistry 類中。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {   
 /**
 *一級緩存:單例池
 *存放已經初始化的bean——成品
 */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /**
 *三級緩存:單例工廠的高速緩存
 *存放生成bean的工廠
 */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    /**
 *二級緩存:早期單例對象的高速緩存
 *存放已經執行個體化但未初始化(未填充屬性)的的bean——半成品
 */
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
}           
Spring循環依賴還能這麼了解 (實在高)

建立過程(簡易版)

如果你是面試突擊,建議把簡易版 背 下來就可以應付面試了

等有時間再看源碼版

假如A依賴B,B依賴A,那麼這兩個類之間形成了一個循環依賴

  • A先開始建立,通過其無參構造方法建立bean的執行個體,并将其執行個體放入到「二級緩存」提前暴露出來。A停止。
  • B開始建立,先去「一級緩存」找A的成品,找不到,再去「二級緩存」裡找,還找不到,再去「三級緩存」裡找,找到了A的建立工廠,通過工廠,拿到A的半成品,并将A放到「二級緩存」。
  • 拿到A後,B完成建立,将自己放入「一級緩存」。
  • 此時A繼續建立,同樣從「一級緩存」開始找,拿到B後完成建立,将自己放入「一級緩存」。

建立過程(源碼版)

源碼版建議配合spring源碼邊debug邊食用。

1、當我們在調用getBean()擷取bean時,實際調用的是doGetBean() 方法。doGetBean() 想要擷取 beanA ,于是調用 getSingleton() 方法從緩存中查找 beanA

2、在 getSingleton() 方法中,從「一級緩存」中查找,沒有,傳回 null

3、doGetBean() 方法中擷取到 beanA 為 null ,于是走對應的處理邏輯,調用 getSingleton() 的重載方法(參數為 ObjectFactory 的)

4、在 getSingleton()方法中,先将 beanA_name 添加到一個集合中,用于标記該 bean 正在建立中,然後回調匿名内部類的 createBean 方法

5、進入 AbstractAutowireCapableBeanFactory#doCreateBean,先反射調用構造器建立出 beanA 的執行個體,然後判斷,是否為單例,是否允許提前暴露引用(對于單例一般為true)、是否正在建立中(即是否是在第四步的集合中)判斷為 true 則将 beanA 添加到「三級緩存」中

6、對 beanA 進行屬性填充,此時檢測到 beanA 依賴于 beanB ,于是查找 beanB

7、調用 doGetBean() 方法,和上面 beanA 的過程一樣,到緩存中查詢 beanB ,沒有則建立,然後給 beanB 填充屬性

8、此時 beanB 依賴于 beanA ,調用 getSingleton() 擷取 beanA ,依次從一級、二級、三級緩存中找、此時從「三級緩存」中擷取到 beanA 的建立工廠,通過建立工廠擷取到 singletonObject ,此時這個 singletonObject 指向的就是上面在 doCreateBean() 方法中執行個體化的 beanA

9、這樣 beanB 就擷取到了 beanA 的依賴,于是 beanB 順利完成初始化,并将 beanA 從「三級緩存」移動到「二級緩存」中

10、随後 beanA 繼續他的屬性填充工作,此時也擷取到了 beanB ,beanA 也随之完成了建立,回到 getSingleton() 方法中繼續向下執行,将 beanA 從「二級緩存」移動到「一級緩存」中

Spring循環依賴還能這麼了解 (實在高)
Spring循環依賴還能這麼了解 (實在高)

學習更多JAVA知識與技巧,關注與私信部落客(學習)免費學習領取JAVA 課件,

源碼,視訊,安裝包等等等

原文作者:月伴飛魚

原文出處:https://mp.weixin.qq.com/s?__biz=MzUyOTg1OTkyMA==&mid=2247487555&idx=1&sn=60152b6c79eb5739afd53e52d6659f45