天天看點

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

作者:Java大資料進階架構師

我相信有輸入一定要有輸出,歡迎關注我一起學習最幹的技術架構底層原理。

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

相信點進來的同學,一定是和我一樣愛學習的好同學,哈哈,或許面試中遇到過,或許刷8股文的時候看到過Spring循環依賴相關的面試題,Spring循環依賴為啥要使用三級緩存來解決這個面試題我被問過很多很多次,而且很難講清楚。我覺得看過Spring源碼後還是要記錄下來Spring解決循環依賴的設計思路和技術愛好者分享一波。

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

第一次被面試問過之後,我翻了很多很多文章,有一定的啟發,但是很少有根據實際案例文章來講解的,導緻我很容易忘記,而且面試時要給面試官講清楚真的很難,後面我下定決心一定要根據實際例子徹底弄清楚循環依賴,今天我就來使用實際例子來邊調試邊學習Spring循環依賴到底是如何處理的。

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

說明

看這篇文章的同學需要有對Spring ioc和di流程有了解,知道Spring bean建立和bean屬性填充。

回憶從Spring容器擷取bean

首先,我們可以找到Spring擷取bean的方法,它會從三個緩存中擷取。

//一級緩存,存儲可以直接使用的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二級緩存 存儲不完整對象 不能直接用的 循環依賴時,某些依賴的屬性沒有設定,這個時候bean就是不完整對象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //三級緩沖 存儲bean工廠 可以延遲調用執行個體化 使用的時候才調用工廠方法擷取對象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //從Spring容器擷取bean對象方法
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //一級緩存
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
            //二級緩存
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                    //三級緩存
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }           
這裡為什麼需要3個緩存呢?為啥2個不行?1個也不行?我們下面開始來看到底為什麼?

模拟循環依賴場景

A,B類分别有對方的類屬性

@Service
public class A {
    
    private B b;
}           
@Service
public class B {

    private A a;
}           

模拟循環依賴互相手動注入

a對象依賴b對象,b對象又依賴a對象,屬性注入流程就是先執行個體化B,注入A,然後再填充A

A a = new A();

B b = new B();

b.setA(a); //B裡面注入不完整的A了

a.setB(b); //注入依賴的對象,A裡面有B了,a注入完成變成完整的a           

調試Spring循環依賴注入處理流程

相同的場景,看看Spring如何處理。

第一步,建立A類的對象a

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

在Spring中,A類執行個體化後,緩存了一個工廠,而不是執行個體A,why?

第二步 開始注入a對象的屬性b

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

a注入b的的時候,緩存注冊工廠類,而不是b執行個體

第三步 b對象又開始注入屬性a對象

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

b對象又開始注入a對象了,這個時候a隻有對應的工廠。

通過A工廠傳回了a的代理對象,将代理對象放入到提早暴露的緩存earlySingletonObjects(二級緩存),這個時候的a并沒有将b注入完成,還是一個半成品,是以a還是先放入到一個中間緩存。

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

第四步 b對象注入a對象

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

這個時候B注入完成了,A還沒注入完成。B裡面的A裡面的B沒有指派

第五步 注入完整B對象,(實際還不是完整的)

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

第6步 a對象的b屬性注入

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

最後将A對象的B屬性注入,這個時候A對象完整了,是以B對象也完整了(彌補上一步驟)

完整a對象圖

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

完整b對象圖

面試問Spring循環依賴?結果答不上來?今天用代碼調試讓你搞清

總結一下,為什麼使用三級緩存?

下次面試再被問到循環依賴的問題,我會怎麼來給面試官說明白呢?

1、Spring通過3個緩存map來解決循環依賴的問題,分别是singletonObjects,singletonFactories,earlySingletonObjects

2、singletonObjects這個緩存是存儲完整的對象,可以直接使用的。

3、singletonFactories這個緩存是為了延遲初始化,是解決循環依賴的關鍵,存在循環依賴注入時,可能注入的對象需要被代理,這個工廠類來執行個體化一個代理類。

4、earlySingletonObjects這個緩存是為了在a注入b,而b又注入a這個階段,a是一個半成品,需要用來存儲a這種半成品bean的狀态。

需要等待a将b注入之後,這個緩存就要移除。

5、singletonFactories和earlySingletonObjects都是存儲對象的中間狀态,依賴它們保證最終注入的對象是完整的,依賴注入完成後會被清除。

原文:https://juejin.cn/post/7230361738590617637