1. 程式人生 > 實用技巧 >讓阿里面試官都讚不絕口的Spring解讀,沒人比我更懂迴圈依賴!

讓阿里面試官都讚不絕口的Spring解讀,沒人比我更懂迴圈依賴!

一、前言

這一篇博文主要講一下我們spring是怎麼解決迴圈依賴的問題的。

二、什麼是迴圈依賴

首先我們需要明確,什麼是迴圈依賴呢?這裡舉一個簡單的例子:

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



以這個例子來看,我們聲明瞭ab兩個bean,且a中需要注入一個bb中需要注入一個a

結合我們上篇博文的bean生命週期的知識,我們來模擬一下這兩個bean建立的流程:

如果沒有快取的設計,我們的虛線所示的分支將永遠無法到達,導致出現無法解決的迴圈依賴問題....

三、三級快取設計

1. 自己解決迴圈依賴問題

現在,假如我們是spring的架構師,我們應該怎麼解決這個迴圈依賴問題呢?

1.1. 流程設計

首先如果要解決這個問題,我們的目標應該是要把之前的級聯的無限建立流程切到,也就是說我們的流程要變為如下所示:

也就是說,我們需要在B例項建立後,注入A的時候,能夠拿到A的例項,這樣才能打破無限建立例項的情況。

B例項的初始化流程,是在A例項建立之後,在populateBean方法中進行依賴注入時觸發的。那麼如果我們B例項化過程中,想要拿到A的例項,那麼A例項必須在createBeanInstance建立例項後(例項都沒有就啥也別說了)、populateBean

方法呼叫之前,就暴露出去,讓B能通過getBean獲取到!(同學們認真想一下這個流程,在現有的流程下改造,是不是隻能夠這樣操作?自己先想清楚這個流程,再去結合spring原始碼驗證,這一塊的知識點你以後想忘都忘不掉)

那麼結合我們的思路,我們再修改一下流程圖:

1.2. 虛擬碼實現

流程已經設計好了,那麼我們其實也可以出一下這個流程的虛擬碼(虛擬碼就不寫加鎖那些流程了):

// 正真已經初始化完成的map
private Map<String, Object> singleMap = new ConcurrentHashMap<>(16);
// 快取的map
private
Map<String, Object> cacheMap = new ConcurrentHashMap<>(16); protected Object getBean(final String beanName) { // 先看一下目標bean是否完全初始化完了,完全初始化完直接返回 Object single = singleMap.get(beanName); if (single != null) { return single; } // 再看一下目標bean例項是否已經建立,已經建立直接返回 single = cacheMap.get(beanName); if (single != null) { return single; } // 建立例項 Object beanInstance = createBeanInstance(beanName); // 例項建立之後,放入快取 // 因為已經建立例項了,這個時候這個例項的引用暴露出去已經沒問題了 // 之後的屬性注入等邏輯還是在這個例項上做的 cacheMap.put(beanName, beanInstance); // 依賴注入,會觸發依賴的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法呼叫 initializeBean(beanName, beanInstance); // 從快取移除,放入例項map singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) return beanInstance; }



可以看到,如果我們自己實現一個快取結構來解決迴圈依賴的問題的話,可能只需要兩層結構就可以了,但是spring卻使用了3級快取,它有哪些不一樣的考量呢?

2. Spring原始碼

我們已經知道該怎麼解決迴圈依賴問題了,那麼現在我們就一起看一下spring原始碼,看一下我們的分析是否正確。

由於之前我們已經詳細講過整個bean的生命週期了,所以這裡就只挑三級快取相關的程式碼段來講了,會跳過比較多的程式碼,同學們如果有點懵,可以溫習一下萬字長文講透bean的生命週期

2.1. Spring的三級快取設計

2.1.1. 三級快取原始碼

首先,在我們的AbstractBeanFactory#doGetBean的邏輯中:

// 初始化是通過getBean觸發bean建立的,依賴注入最終也會使用getBean獲取依賴的bean的例項
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // 獲取bean例項
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // beanFactory相關,之後再講
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        // 跳過一些程式碼
        // 建立bean的邏輯
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
        // 跳過一些程式碼
    }
    // 跳過一些程式碼
    // 返回bean例項
    return (T) bean;
}



可以看到,如果我們使用getSingleton(beanName)直接獲取到bean例項了,是會直接把bean例項返回的,我們一起看一下這個方法(這個方法屬於DefaultSingletonBeanRegistry):

// 一級快取,快取正常的bean例項
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二級快取,快取還未進行依賴注入和初始化方法呼叫的bean例項
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 三級快取,快取bean例項的ObjectFactory
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先嚐試中一級快取獲取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 獲取不到,並且當前需要獲取的bean正在建立中
    // 第一次容器初始化觸發getBean(A)的時候,這個isSingletonCurrentlyInCreation判斷一定為false
    // 這個時候就會去走建立bean的流程,建立bean之前會先把這個bean標記為正在建立
    // 然後A例項化之後,依賴注入B,觸發B的例項化,B再注入A的時候,會再次觸發getBean(A)
    // 此時isSingletonCurrentlyInCreation就會返回true了
    
    // 當前需要獲取的bean正在建立中時,代表出現了迴圈依賴(或者一前一後併發獲取這個bean)
    // 這個時候才需要去看二、三級快取
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加鎖了
        synchronized (this.singletonObjects) {
            // 從二級快取獲取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 二級快取也沒有,並且允許獲取早期引用的話 - allowEarlyReference傳進來是true
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                // 從三級快取獲取ObjectFactory
                if (singletonFactory != null) {
                    // 通過ObjectFactory獲取bean例項
                    singletonObject = singletonFactory.getObject();
                    // 放入二級快取
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 從三級快取刪除
                    // 也就是說對於一個單例bean,ObjectFactory#getObject只會呼叫到一次
                    // 獲取到早期bean例項之後,就把這個bean例項從三級快取升級到二級快取了
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 不管從哪裡獲取到的bean例項,都會返回
    return singletonObject;
}



一二級快取都好理解,其實就可以理解為我們虛擬碼裡面的那兩個Map,但是這個三級快取是怎麼回事?ObjectFactory又是個什麼東西?我們就先看一下這個ObjectFactory的結構:

@FunctionalInterface
public interface ObjectFactory<T> {
    // 好吧,就是簡簡單單的一個獲取例項的函式介面而已
    T getObject() throws BeansException;
}

我們回到這個三級快取的結構,二級快取是是在getSingleton方法中put進去的,這跟我們之前分析的,建立bean例項之後放入,好像不太一樣?那我們是不是可以推斷一下,其實建立bean例項之後,是放入三級快取的呢(總之例項建立之後是需要放入快取的)?我們來跟一下bean例項化的程式碼,主要看一下上一篇時刻意忽略掉的地方:

// 程式碼做了很多刪減,只把主要的邏輯放出來的
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    // 建立bean例項
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    final Object bean = instanceWrapper.getWrappedInstance();
    // beanPostProcessor埋點呼叫
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    
    // 重點是這裡了,如果是單例bean&&允許迴圈依賴&&當前bean正在建立
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 加入三級快取
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    Object exposedObject = bean;
    try {
        // 依賴注入
        populateBean(beanName, mbd, instanceWrapper);
        // 初始化方法呼叫
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(...); 
    }

    if (earlySingletonExposure) {
        // 第二個引數傳false是不會從三級快取中取值的
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 如果發現二級快取中有值了 - 說明出現了迴圈依賴
            if (exposedObject == bean) {
                // 並且initializeBean沒有改變bean的引用
                // 則把二級快取中的bean例項返回出去
                exposedObject = earlySingletonReference;
            }
        }
    }
    
    try {
        // 註冊銷燬邏輯
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(...);
    }

    return exposedObject;
}



可以看到,初始化一個bean是,建立bean例項之後,如果這個bean是單例bean&&允許迴圈依賴&&當前bean正在建立,那麼將會呼叫addSingletonFactory加入三級快取:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 加入三級快取
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

也就是說我們虛擬碼中的這一段有了:

// 建立例項
Object beanInstance = createBeanInstance(beanName);
// 例項建立之後,放入快取
// 因為已經建立例項了,這個時候這個例項的引用暴露出去已經沒問題了
// 之後的屬性注入等邏輯還是在這個例項上做的
cacheMap.put(beanName, beanInstance);
複製程式碼
那麼接下來,完全例項化完成的bean又是什麼時候塞入我們的例項Map(一級快取)singletonObjects的呢?

這個時候我們就要回到呼叫createBean方法的這一塊的邏輯了:

if (mbd.isSingleton()) {
    // 我們回到這個位置
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

可以看到,我們的createBean建立邏輯是通過一個lamdba語法傳入getSingleton方法了,我們進入這個方法看一下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 一級快取拿不到
            // 注意一下這個方法,這裡會標記這個bean正在建立
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // 呼叫外部傳入的lamdba,即createBean邏輯
                // 獲取到完全例項化好的bean
                // 需要注意的是,這個時候這個bean的例項已經在二級快取或者三級快取中了
                // 三級快取:bean例項建立後放入的,如果沒有迴圈依賴/併發獲取這個bean,那會一直在三級快取中
                // 二級快取:如果出現迴圈依賴,第二次進入getBean->getSingleton的時候,會從三級快取升級到二級快取
                singletonObject = singletonFactory.getObject();
                // 標記一下
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // 這裡是從正在建立的列表移除,到這裡這個bean要麼已經完全初始化完成了
                // 要麼就是初始化失敗,都需要移除的
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 如果是新初始化了一個單例bean,加入一級快取
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}



哈哈,加入例項Map(一級快取)singletonObjects的邏輯明顯就是在這個addSingleton中了:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 這個邏輯應該一點也不意外吧
        // 放入一級快取,從二、三級快取刪除,這裡就用判斷當前bean具體是在哪個快取了
        // 反正都要刪的
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

也就是說,我們虛擬碼的這一塊在spring裡面也有對應的體現,完美:

// 初始化方法呼叫
initializeBean(beanName, beanInstance);
// 從快取移除,放入例項map
singleMap.put(beanName, beanInstance);
cacheMap.remove(beanName)



就這樣,spring通過快取設計解決了迴圈依賴的問題。

2.1.2. 三級快取解決迴圈依賴流程圖

什麼,看完程式碼之後還是有點模糊?那麼把我們的流程圖再改一下,按照spring的流程來:

2.1.3. 三級快取解決迴圈依賴虛擬碼

看完圖還覺得不清晰的話,我們把所有spring中三級快取相關的程式碼彙總到一起,用虛擬碼的方式,拍平成一個方法,大家應該感覺會更清晰了:

// 一級快取
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二級快取
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三級快取
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

protected Object getBean(final String beanName) {
    // !以下為getSingleton邏輯!
    // 先從一級快取獲取
    Object single = singletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 再從二級快取獲取
    single = earlySingletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 從三級快取獲取objectFactory
    ObjectFactory<?> objectFactory = singletonFactories.get(beanName);
    if (objectFactory != null) {
        single = objectFactory.get();
        // 升到二級快取
        earlySingletonObjects.put(beanName, single);
        singletonFactories.remove(beanName);
        return single;
    }
    // !以上為getSingleton邏輯!
    
    // !以下為doCreateBean邏輯
    // 快取完全拿不到,需要建立
    // 建立例項
    Object beanInstance = createBeanInstance(beanName);
    // 例項建立之後,放入三級快取
    singletonFactories.put(beanName, () -> return beanInstance);
    // 依賴注入,會觸發依賴的bean的getBean方法
    populateBean(beanName, beanInstance);
    // 初始化方法呼叫
    initializeBean(beanName, beanInstance);
    
    // 依賴注入完之後,如果二級快取有值,說明出現了迴圈依賴
    // 這個時候直接取二級快取中的bean例項
    Object earlySingletonReference = earlySingletonObjects.get(beanName);
    if (earlySingletonReference != null) {
        beanInstance = earlySingletonObject;
    }
    // !以上為doCreateBean邏輯
    
    // 從二三快取移除,放入一級快取
    singletonObjects.put(beanName, beanInstance);
    earlySingletonObjects.remove(beanName);
    singletonFactories.remove(beanName);
    
    return beanInstance;
}



把所有邏輯放到一起之後會清晰很多,同學們只需要自行模擬一遍,再populateBean中再次呼叫getBean邏輯進行依賴注入,應該就能捋清楚了。

2.1.4. 標記當前bean正在建立

在我們剛剛看到的將bean例項封裝成ObjectFactory並放入三級快取的流程中,有一個判斷是當前bean是正在建立,這個狀態又是怎麼判斷的呢:

// 重點是這裡了,如果是單例bean&&允許迴圈依賴&&當前bean正在建立
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // 加入三級快取
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

我們看一下這個isSingletonCurrentlyInCreation的邏輯:

private final Set<String> singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}



可以看到額,其實就是判斷當前beanName是不是在這個singletonsCurrentlyInCreation容器中,那麼這個容器中的值又是什麼時候操作的呢?

希望同學們還記得getSingleton(beanName, singletonFactory)中有呼叫的beforeSingletonCreationafterSingletonCreation

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 一級快取拿不到
            // 注意一下這個方法,這裡會標記這個bean正在建立
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // 呼叫外部傳入的lamdba,即createBean邏輯
                singletonObject = singletonFactory.getObject();
                // 標記一下
                newSingleton = true;
            }
            catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // 這裡是從正在建立的列表移除,到這裡這個bean要麼已經完全初始化完成了
                // 要麼就是初始化失敗,都需要移除的
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 如果是新初始化了一個單例bean,加入一級快取
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}



我們現在來看一下這兩個方法的邏輯:

protected void beforeSingletonCreation(String beanName) {
    // 加入singletonsCurrentlyInCreation,由於singletonsCurrentlyInCreation是一個set
    // 如果加入失敗的話,說明在建立兩次這個bean
    // 這個時候會丟擲迴圈依賴異常
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

protected void afterSingletonCreation(String beanName) {
    // 從singletonsCurrentlyInCreation中刪除
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

可以看到,我們這兩個方法主要就是對singletonsCurrentlyInCreation容器進行操作的,inCreationCheckExclusions這個容器可以不用管它,這名稱一看就是一些白名單之類的配置。

這裡需要主要的是beforeSingletonCreation中,如果singletonsCurrentlyInCreation.add(beanName)失敗的話,是會丟擲BeanCurrentlyInCreationException的,這代表spring遇到了無法解決的迴圈依賴問題,此時會丟擲異常中斷初始化流程,畢竟單例的bean不允許被建立兩次。

2.2. 為什麼要設計為三級結構?

2.2.1. 只做兩級快取會有什麼問題?

其實到這裡,我們已經清楚,三級快取的設計已經成功的解決了迴圈依賴的問題。

可是按我們自己的設計思路,明明只需要兩級快取就可以解決,spring卻使用了三級快取,難道是為了炫技麼?

這個時候,就需要我們再細緻的看一下bean初始化過程了:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {
    // ...
    if (earlySingletonExposure) {
        // 放入三級快取
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        // 這裡這個引用被替換了
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // ...
    return exposedObject;
}

仔細觀察,initializeBean方法是可能返回一個新的物件,從而把createBeanInstance建立的bean例項替換掉的:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // 呼叫aware介面
    invokeAwareMethods(beanName, bean);
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 埋點
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 初始化方法
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(...);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 埋點
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}



可以看到,我們的postProcessBeforeInitializationpostProcessAfterInitialization的埋點方法都是有可能把我們的bean替換掉的。

那麼結合整個流程來看,由於我們放入快取之後,initializeBean方法中可能存在替換bean的情況,如果只有兩級快取的話:

這會導致B中注入的A例項與singletonObjects中儲存的AA例項不一致,而之後其他的例項注入a時,卻會拿到singletonObjects中的AA例項,這樣肯定是不符合預期的。

2.2.2. 三級快取是如何解決問題的

那麼這個問題應該怎麼解決呢?

這個時候我們就要回到新增三級快取的地方看一下了。addSingletonFactory的第二個引數就是一個ObjectFactory,並且這個ObjectFactory最終將會放入三級快取,現在我們再回頭看呼叫addSingletonFactory的地方:

 // 加入三級快取
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

熟悉lamdba語法的同學都知道,getEarlyBeanReference其實就是放入三級快取中的ObjectFactorygetObject方法的邏輯了,那我們一起來看一下,這個方法是做了什麼:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 呼叫了beanPostProcessor的一個埋點方法
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    // 返回的是埋點替換的bean
    return exposedObject;
}



咦,這裡也有個埋點,可以替換掉bean的引用。

原來為了解決initializeBean可能替換bean引用的問題,spring就設計了這個三級快取,他在第三級裡儲存了一個ObjectFactory,其實具體就是getEarlyBeanReference的呼叫,其中提供了一個getEarlyBeanReference的埋點方法,通過這個埋點方法,它允許開發人員把需要替換的bean,提早替換出來。

比如說如果在initializeBean方法中希望把A換成AA(這個邏輯肯定是通過某個beanPostProcessor來做的),那麼你這個beanPostProcessor可以同時提供getEarlyBeanReference方法,在出現迴圈依賴的時候,可以提前把A->AA這個邏輯做了,並且initializeBean方法不再做這個A->AA的邏輯,並且,當我們的迴圈依賴邏輯走完,A建立->注入B->觸發B初始化->注入A->執行快取邏輯獲取AA例項並放入二級快取->B初始化完成->回到A的初始化邏輯時,通過以下程式碼:

protected Object doCreateBean(...) {
    populateBean(beanName, mbd, instanceWrapper);
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                // 如果二級快取存在,直接使用二級快取
                exposedObject = earlySingletonReference;
            }
        }
    }
    return exposedObject;
}

這樣就能保證當前bean中注入的AAsingletonObjects中的AA例項是同一個物件了。

將會把二級快取中的AA直接返回,這是就能保證B中注入的AA例項與spring管理起來的最終的AA例項是同一個了。

整個流程梳理一下就是這樣:

2.2.3. 三級快取的實際應用

既然設計了這個三級快取,那麼肯定是有實際需求的,我們上面分析了一大堆,現在正好舉一個例子看一下,為什麼spring需要三級快取。

我們都知道,SpringAOP功能,是通過生成動態代理類來實現的,而最後我們使用的也都是代理類例項而不是原始類例項。而AOP代理類的建立,就是在initializeBean方法的postProcessAfterInitialization埋點中,我們直接看一下getEarlyBeanReferencepostProcessAfterInitialization這兩個埋點吧(具體類是AbstractAutoProxyCreator,之後講AOP的時候會細講):

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
    // 如果出現迴圈依賴,getEarlyBeanReference會先被呼叫到
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 這個時候把當前類放入earlyProxyReferences
        this.earlyProxyReferences.put(cacheKey, bean);
        // 直接返回了一個代理例項
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // 注意這個判斷,如果出現了迴圈依賴,這個if塊是進不去的
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 如果沒有出現迴圈依賴,會在這裡建立代理類
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
}



就這樣,Spring巧妙的使用三級快取來解決了這個不同例項的問題。當然,如果我們需要自己開發類似代理之類的可能改變bean引用的功能時,也需要遵循getEarlyBeanReference方法的埋點邏輯,學習AbstractAutoProxyCreator中的方式,才能讓spring按照我們的預期來工作。

四、三級快取無法解決的問題

1. 構造器迴圈依賴

剛剛講了很多三級快取的實現,以及它是怎麼解決迴圈依賴的問題的。

但是,是不是使用了三級快取,就能解決所有的迴圈依賴問題呢?

當然不是的,有一個特殊的迴圈依賴,由於java語言特性的原因,是永遠無法解決的,那就是構造器迴圈依賴。

比如以下兩個類:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
}
public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
}



拋開Spring來講,同學們你們有辦法讓這兩個類例項化成功麼?

該不會有同學說,這有何難看我的:

// 你看,這樣不行麼~
final A a = new A(new B(a));

不好意思,這個真的不行,不信可以去試試。從語法上來講,java的語言特性決定了不允許使用未初始化完成的變數。我們只能無限套娃:

// 這樣明顯就沒有解決問題,是個無限套娃的死迴圈
final A a = new A(new B(new A(new B(new A(new B(...))))));



所以,連我們都無法解決的問題,就不應該強求spring來解決了吧~

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
}
@Service
public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
}



啟動之後,果然報錯了:

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?



2. Spring真的對構造器迴圈依賴束手無策麼?

難道,spring對於這種迴圈依賴真的束手無策了麼?其實不是的,spring還有@Lazy這個大殺器...只需要我們對剛剛那兩個類小小的改造一下:

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt() {
        System.out.println("in a prt");
    }
}
@Service
public class B {
    private final A a;
    public B(@Lazy final A a) {
        this.a = a;
    }
    public void prt() {
        a.prt();
    }
}
// 啟動
@Test
public void test() {
    applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    B bean = applicationContext.getBean(B.class);
    bean.prt();
}

都說了成功了,執行結果同學們也能猜到了吧:

in a prt

(同學們也可以自己嘗試一下~

3. @Lazy原理

這個時候我們必須要想一下,spring是怎麼通過 @Lazy來繞過我們剛剛解決不了的無限套娃問題了。

因為這裡涉及到之前沒有細節的引數注入時候的引數解析問題,我這邊就不帶大家從入口處一步一步深入了,這邊直接空降到目的碼DefaultListableBeanFactory#resolveDependency

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // 跳過...
    // 這個地方是我們獲取依賴的地方
    // 嘗試獲取一個懶載入代理
    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
        descriptor, requestingBeanName);
    if (result == null) {
        // 如果沒獲取到懶載入代理,就直接去獲取bean例項了,這裡最終會呼叫getBean
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }
    return result;
}



我們直接看一下這個getLazyResolutionProxyIfNecessary,這個方法就是獲取LazyProxy的地方了:

public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {

    @Override
    @Nullable
    public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
        // 如果是懶載入的,就構建一個懶載入的代理
        return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
    }
    // 判斷是否是懶載入的,主要就是判斷@Lazy註解,簡單看下就好了
    protected boolean isLazy(DependencyDescriptor descriptor) {
        for (Annotation ann : descriptor.getAnnotations()) {
            Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
            if (lazy != null && lazy.value()) {
                return true;
            }
        }
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            Method method = methodParam.getMethod();
            if (method == null || void.class == method.getReturnType()) {
                Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
                if (lazy != null && lazy.value()) {
                    return true;
                }
            }
        }
        return false;
    }

    protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
        final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
        // 構造了一個TargetSource
        TargetSource ts = new TargetSource() {
            @Override
            public Class<?> getTargetClass() {
                return descriptor.getDependencyType();
            }
            @Override
            public boolean isStatic() {
                return false;
            }
            @Override
            public Object getTarget() {
                // 再對應的getTarget方法裡,才會去正真載入依賴,進而呼叫getBean方法
                Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
                if (target == null) {
                    Class<?> type = getTargetClass();
                    if (Map.class == type) {
                        return Collections.emptyMap();
                    }
                    else if (List.class == type) {
                        return Collections.emptyList();
                    }
                    else if (Set.class == type || Collection.class == type) {
                        return Collections.emptySet();
                    }
                    throw new NoSuchBeanDefinitionException(...);
                }
                return target;
            }
            @Override
            public void releaseTarget(Object target) {
            }
        };
        // 建立代理工廠ProxyFactory
        ProxyFactory pf = new ProxyFactory();
        pf.setTargetSource(ts);
        Class<?> dependencyType = descriptor.getDependencyType();
        if (dependencyType.isInterface()) {
            pf.addInterface(dependencyType);
        }
        // 建立返回代理類
        return pf.getProxy(beanFactory.getBeanClassLoader());
    }
}

同學們可能對TargetSourceProxyFactory這些不熟悉,沒關係,這不妨礙我們理解邏輯。

從原始碼我們可以看到,對於@Lazy的依賴,我們其實是返回了一個代理類(以下稱為LazyProxy)而不是正真通過getBean拿到目標bean注入。而真正的獲取bean的邏輯,被封裝到了一個TargetSource類的getTarget方法中,而這個TargetSource類最終被用來生成LazyProxy了,那麼我們是不是可以推測,LazyProxy應該持有這個TargetSource物件。

而從我們懶載入的語意來講,是說真正使用到這個bean(呼叫這個bean的某個方法時)的時候,才對這個屬性進行注入/初始化。

那麼對於當前這個例子來講,就是說其實B建立的時候,並沒有去呼叫getBean("a")去獲取構造器的引數,而是直接生成了一個LazyProxy來做B構造器的引數,而B之後正真呼叫到A的方法時,才會去呼叫TargetSource中的getTarget獲取A例項,即呼叫getBean("a"),這個時候A早就例項化好了,所以也就不會有迴圈依賴問題了。

4. 虛擬碼描述

還是同樣,我們可以用虛擬碼來描述一下這個流程,虛擬碼我們就直接用靜態代理來描述了:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt() {
        System.out.println("in a prt");
    }
}

public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
    public void prt() {
        a.prt();
    }
}
// A的懶載入代理類
public class LazyProxyA extends A {
    private A source;
    private final Map<String, Object> ioc;
    private final String beanName;
    public LazyProxyA(Map<String, Object> ioc, String beanName) {
        super(null);
        this.ioc = ioc;
        this.beanName = beanName;
    }
    @Override
    public void prt() {
        if (source == null) {
            source = (A) ioc.get(beanName);
        }
        source.prt();
    }
}



那麼整個初始化的流程簡單來描述就是:

Map<String, Object> ioc = new HashMap<>();
void init() {
    B b = new B(new LazyProxyA(ioc, "a"));
    ioc.put("b", b);
    A a = new A((B)ioc.get("b"));
    ioc.put("a", a);
}

我們也模擬一下執行:

void test() {
    // 容器初始化
    init();
    B b = (B)ioc.get("b");
    b.prt();
}

當然是能成功列印的:

in a prt

六、總結

關於迴圈依賴的問題,Spring提供了通過設計快取的方式來解決的,而設計為三級快取,主要是為了解決bean初始化過程中,例項被放入快取之後,例項的引用還可能在呼叫initializeBean方法時被替換的問題。

對於構造器的迴圈依賴,三級快取設計是無法解決的,這屬於java語言的約束;但是spring提供了一種使用@Lazy的方式,繞過這個限制,使得構造器的迴圈依賴在特定情況下(迴圈鏈中的某個注入打上@Lazy註解)也能解決。

版權所屬:歸原作者所有

聲援博主:您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波推薦不要忘記哦!!!

別忘了點推薦留下您來過的痕跡

交流看我公告


作者:小希子
連結:https://juejin.im/post/6860785780307492871
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。