Spring是怎么解決循環依賴的?
首先,Spring 解決循環依賴有兩個前提條件:
- 不全是構造器方式的循環依賴
- 必須是單例
基於上面的問題,我們知道Bean的生命周期,本質上解決循環依賴的問題就是三級緩存,通過三級緩存提前拿到未初始化的對象。
第一級緩存:用來保存實例化、初始化都完成的對象
第二級緩存:用來保存實例化完成,但是未初始化完成的對象
第三級緩存:用來保存一個對象工廠,提供一個匿名內部類,用於創建二級緩存中的對象
假設一個簡單的循環依賴場景,A、B互相依賴。
A對象的創建過程:
- 創建對象A,實例化的時候把A對象工廠放入三級緩存
-
A注入屬性時,發現依賴B,轉而去實例化B
-
同樣創建對象B,注入屬性時發現依賴A,一次從一級到三級緩存查詢A,從三級緩存通過對象工廠拿到A,把A放入二級緩存,同時刪除三級緩存中的A,此時,B已經實例化並且初始化完成,把B放入一級緩存。
-
接着繼續創建A,順利從一級緩存拿到實例化且初始化完成的B對象,A對象創建也完成,刪除二級緩存中的A,同時把A放入一級緩存
-
最后,一級緩存中保存着實例化、初始化都完成的A、B對象
因此,由於把實例化和初始化的流程分開了,所以如果都是用構造器的話,就沒法分離這個操作,所以都是構造器的話就無法解決循環依賴的問題了。
具體的方法:
- 調用doGetBean()方法,想要獲取beanA,於是調用getSingleton()方法從緩存中查詢beanA
- getSingleton()方法屮,從一級緩存中查找,沒有,返回null
- doGetBean()方法屮獲取到的beanA為null, 於是走對應的處理邏輯,調用getSingleton()的重載方法(參數為ObjectFactory的)
- 在getSingleton()方法中,先將beanA_name添加到一個集合中,用於標記bean創建中。然后回調匿名內部類的creatBean方法
- 進入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調用構選器創建出beanA的實例。然后判斷:是否為單例、足否允許提前暴露引用(對於單例一般為true)、是否正在創建中(即是否在第四步的集合中)。判斷力true則將beanA添加到【三級緩存】中。
- 對beanA進行屬性填充,此時檢測到 beanA依賴beanB,於是開始查找beanB
- 調用doGetBean()方法,和上面beanA的過程一樣,到緩存中查找beanB,沒行則創建,然后給beanB填充屬性。
- 此時beanB依賴於beanA,調用getSingleton()獲取beanA,依次從一級、二級、三級緩存中找,此時從三級緩存中獲取到beanA的創建工廠,通過創建工廠獲取到singletonObject,此這個 singletonObject指向的就是在上面在在doCreateBean()方法中實例化的beanA 。
- 這樣beanB就獲取到beanA的依賴,於是beanB順利完成實例化,並將beanA從三級緩存移動到二級緩存中。
- 隨后 beanA繼續他的屬性填充工作,此時也獲取到beanB, beanA也隨之完成創建,回到getSingleton()方法屮繼續向下執行,將beanA從二級緩存移動到一級緩存中。
為什么要三級緩存?二級不行嗎?
不可以,主要是為了生成代理對象。
因為三級緩存中放的是生成具體對象的匿名內部類,value為一個lambda表達式,他可以生成代理對象,也可以是普通的實例對象。
使用三級緩存主要是為了保證不管什么時候使用的都是一個對象。
假設只有二級緩存的情況,往二級緩存中放的顯示一個普通的Bean對象,BeanPostProcessor
去生成代理對象之后,覆蓋掉二級緩存中的普通Bean對象,那么多線程環境下可能取到的對象就不一致了。