spring成神之路第五十六篇:spring到底為什么要用三級緩存


今天來聊一個面試中經常會被問到的問題,咱們一起必須把這個問題搞懂。

問題:spring 中為什么需要用三級緩存來解決這個問題?用二級緩存可以么?

我先給出答案:不可用

這里先聲明下:

本文未指明 bean scope 默認情況下,所有 bean 都是單例的,即 scope 是 singleton,即下面所有問題都是在單例的情況下分析的。

代碼中注釋很詳細,一定要注意多看代碼中的注釋。

1、循環依賴相關問題

1、什么是循環依賴?

2、循環依賴的注入對象的 2 種方式:構造器的方式、setter 的方式

3、構造器的方式詳解

4、spring 是如何知道有循環依賴的?

5、setter 方式詳解

6、需注意循環依賴注入的是半成品

7、為什么必須用三級緩存?

2、什么是循環依賴?

A 依賴於 B,B 依賴於 A,比如下面代碼

public class A {
    private B b;
}

public class B {
    private A a;
}

3、循環依賴注入對象的 2 種方式

3.1、構造器的方式

通過構造器相互注入對方,代碼如下

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

3.2、setter 的方式

通過 setter 方法注入對方,代碼如下

public class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

4、構造器的方式詳解

4.1、構造器的方式知識點

1、構造器的方式如何注入?

2、循環依賴,構造器的方式,spring 的處理過程是什么樣的?

3、循環依賴構造器的方式案例代碼解析

4.2、構造器的方式如何注入?

再來看一下下面這 2 個類,相互依賴,通過構造器的方式相互注入對方。

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

大家來思考一個問題:2 個類都只能創建一個對象,大家試試着用硬編碼的方式看看可以創建這 2 個類的對象么?

我想大家一眼就看出來了,無法創建。

創建 A 的時候需要先有 B,而創建 B 的時候需要先有 A,導致無法創建成功。

4.3、循環依賴,構造器的方式,spring 的處理過程是什么樣的?

spring 在創建 bean 之前,會將當前正在創建的 bean 名稱放在一個列表中,這個列表我們就叫做 singletonsCurrentlyInCreation,用來記錄正在創建中的 bean 名稱列表,創建完畢之后,會將其從 singletonsCurrentlyInCreation 列表中移除,並且會將創建好的 bean 放到另外一個單例列表中,這個列表叫做 singletonObjects,下面看一下這兩個集合的代碼,如下:

代碼位於org.springframework.beans.factory.support.DefaultSingletonBeanRegistry類中

//用來存放正在創建中的bean名稱列表
private final Set<String> singletonsCurrentlyInCreation =
   Collections.newSetFromMap(new ConcurrentHashMap<>(16));

//用來存放已經創建好的單例bean,key為bean名稱,value為bean的實例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

下面我們來看下面 2 個 bean 的創建過程

@Compontent
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

@Compontent
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

過程如下

1、從singletonObjects查看是否有a,此時沒有
2、准備創建a
3、判斷a是否在singletonsCurrentlyInCreation列表,此時明顯不在,則將a加入singletonsCurrentlyInCreation列表
4、調用a的構造器A(B b)創建A
5、spring發現A的構造器需要用到b
6、則向spring容器查找b,從singletonObjects查看是否有b,此時沒有
7、spring准備創建b
8、判斷b是否在singletonsCurrentlyInCreation列表,此時明顯不在,則將b加入singletonsCurrentlyInCreation列表
9、調用b的構造器B(A a)創建b
10、spring發現B的構造器需要用到a,則向spring容器查找a
11、則向spring容器查找a,從singletonObjects查看是否有a,此時沒有
12、准備創建a
13、判斷a是否在singletonsCurrentlyInCreation列表,上面第3步中a被放到了這個列表,此時a在這個列表中,走到這里了,說明a已經存在創建列表中了,此時程序又來創建a,說明這么一直走下去會死循環,此時spring會彈出異常,終止bean的創建操作。

4.4、通過這個過程,我們得到了 2 個結論

1、循環依賴如果是構造器的方式,bean 無法創建成功,這個前提是 bean 都是單例的,bean 如果是多例的,大家自己可以分析分析。

2、spring 是通過 singletonsCurrentlyInCreation 這個列表來發現循環依賴的,這個列表會記錄創建中的 bean,當發現 bean 在這個列表中存在了,說明有循環依賴,並且這個循環依賴是無法繼續走下去的,如果繼續走下去,會進入死循環,此時 spring 會拋出異常讓系統終止。

判斷循環依賴的源碼在下面這個位置,singletonsCurrentlyInCreation是 Set 類型的,Set 的 add 方法返回 false,說明被 add 的元素在 Set 中已經存在了,然后會拋出循環依賴的異常。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

private final Set<String> singletonsCurrentlyInCreation =
  Collections.newSetFromMap(new ConcurrentHashMap<>(16));

protected void beforeSingletonCreation(String beanName) {
    //bean名稱已經存在創建列表中,則拋出循環依賴異常
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        //拋出循環依賴異常
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

//循環依賴異常
public BeanCurrentlyInCreationException(String beanName) {
    super(beanName,
          "Requested bean is currently in creation: Is there an unresolvable circular reference?");
}

4.5、spring 構造器循環依賴案例

創建類 A

package com.javacode2018.cycledependency.demo1;

import org.springframework.stereotype.Component;

@Component
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

創建類 B

package com.javacode2018.cycledependency.demo1;

import org.springframework.stereotype.Component;

@Component
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

啟動類

package com.javacode2018.cycledependency.demo1;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        //刷新容器上下文,觸發單例bean創建
        context.refresh();
        //關閉上下文
        context.close();
    }
}

運行上面的 main 方法,產生了異常,部分異常信息如下,說明創建 beana的時候出現了循環依賴,導致創建 bean 無法繼續進行,以后大家遇到這個錯誤了,應該可以很快定位到問題了。

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)

5、setter 方式詳解

再來看看 setter 的 2 個類的源碼

public class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

大家試試通過硬編碼的方式來相互注入,很簡單吧,如下面這樣

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

咱們通過硬編碼的方式可以搞成功的,spring 肯定也可以搞成功,確實,setter 循環依賴,spring 可以正常執行。

下面來看 spring 中 setter 循環依賴注入的流程。

6、spring 中 setter 循環依賴注入流程

spring 在創建單例 bean 的過程中,會用到三級緩存,所以需要先了解三級緩存。

6.1、三級緩存是哪三級?

spring 中使用了 3 個 map 來作為三級緩存,每一級對應一個 map

第幾級緩存 對應的 map 說明
第 1 級 Map<String, Object> singletonObjects 用來存放已經完全創建好的單例 bean

beanName->bean 實例
第 2 級 Map<String, Object> earlySingletonObjects 用來存放早期的 bean

beanName->bean 實例
第 3 級 Map<String, ObjectFactory<?>> singletonFactories 用來存放單例 bean 的 ObjectFactory

beanName->ObjectFactory 實例

這 3 個 map 的源碼位於org.springframework.beans.factory.support.DefaultSingletonBeanRegistry類中。

6.2、單例 bean 創建過程源碼解析

代碼入口

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

step1:doGetBean

如下,這個方法首先會調用 getSingleton 獲取 bean,如果可以獲取到,就會直接返回,否則會執行創建 bean 的流程

step2:getSingleton(beanName, true)

源碼如下,這個方法內部會調用getSingleton(beanName, true)獲取 bean,注意第二個參數是true,這個表示是否可以獲取早期的 bean,這個參數為 true,會嘗試從三級緩存singletonFactories中獲取 bean,然后將三級緩存中獲取到的 bean 丟到二級緩存中。

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //從第1級緩存中獲取bean
    Object singletonObject = this.singletonObjects.get(beanName);
    //第1級中沒有,且當前beanName在創建列表中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //從第2級緩存匯總獲取bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            //第2級緩存中沒有 && allowEarlyReference為true,也就是說2級緩存中沒有找到bean且beanName在當前創建列表中的時候,才會繼續想下走。
            if (singletonObject == null && allowEarlyReference) {
                //從第3級緩存中獲取bean
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                //第3級中有獲取到了
                if (singletonFactory != null) {
                    //3級緩存匯總放的是ObjectFactory,所以會調用其getObject方法獲取bean
                    singletonObject = singletonFactory.getObject();
                    //將3級緩存中的bean丟到第2級中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //將bean從三級緩存中干掉
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

step3:getSingleton(String beanName, ObjectFactory<?> singletonFactory)

上面調用getSingleton(beanName, true)沒有獲取到 bean,所以會繼續走 bean 的創建邏輯,會走到下面代碼,如下

進入getSingleton(String beanName, ObjectFactory<?> singletonFactory),源碼如下,只留了重要的部分

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    //從第1級緩存中獲取bean,如果可以獲取到,則自己返回
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null) {
        //將beanName加入當前創建列表中
        beforeSingletonCreation(beanName);
        //①:創建單例bean
        singletonObject = singletonFactory.getObject();
        //將beanName從當前創建列表中移除
        afterSingletonCreation(beanName);
        //將創建好的單例bean放到1級緩存中,並將其從2、3級緩存中移除
        addSingleton(beanName, singletonObject);
    }
    return singletonObject;
}

注意代碼,會調用singletonFactory.getObject()創建單例 bean,我們回頭看看singletonFactory這個變量的內容,如下圖,可以看出主要就是調用createBean這個方法

 

下面我們進入createBean方法,這個內部最終會調用doCreateBean來創建 bean,所以我們主要看doCreateBean

step4:doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    // ①:創建bean實例,通過反射實例化bean,相當於new X()創建bean的實例
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

    // bean = 獲取剛剛new出來的bean
    Object bean = instanceWrapper.getWrappedInstance();

    // ②:是否需要將早期的bean暴露出去,所謂早期的bean相當於這個bean就是通過new的方式創建了這個對象,但是這個對象還沒有填充屬性,所以是個半成品
    // 是否需要將早期的bean暴露出去,判斷規則(bean是單例 && 是否允許循環依賴 && bean是否在正在創建的列表中)
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));

    if (earlySingletonExposure) {
        //③:調用addSingletonFactory方法,這個方法內部會將其丟到第3級緩存中,getEarlyBeanReference的源碼大家可以看一下,內部會調用一些方法獲取早期的bean對象,比如可以在這個里面通過aop生成代理對象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 這個變量用來存儲最終返回的bean
    Object exposedObject = bean;
    //填充屬性,這里面會調用setter方法或者通過反射將依賴的bean注入進去
    populateBean(beanName, mbd, instanceWrapper);
    //④:初始化bean,內部會調用BeanPostProcessor的一些方法,對bean進行處理,這里可以對bean進行包裝,比如生成代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);


    //早期的bean是否被暴露出去了
    if (earlySingletonExposure) {
        /**
         *⑤:getSingleton(beanName, false),注意第二個參數是false,這個為false的時候,
         * 只會從第1和第2級中獲取bean,此時第1級中肯定是沒有的(只有bean創建完畢之后才會放入1級緩存)
         */
        Object earlySingletonReference = getSingleton(beanName, false);
        /**
         * ⑥:如果earlySingletonReference不為空,說明第2級緩存有這個bean,二級緩存中有這個bean,說明了什么?
         * 大家回頭再去看看上面的分析,看一下什么時候bean會被放入2級緩存?
         * (若 bean存在三級緩存中 && beanName在當前創建列表的時候,此時其他地方調用了getSingleton(beanName, false)方法,那么bean會從三級緩存移到二級緩存)
         */
        if (earlySingletonReference != null) {
            //⑥:exposedObject==bean,說明bean創建好了之后,后期沒有被修改
            if (exposedObject == bean) {
                //earlySingletonReference是從二級緩存中獲取的,二級緩存中的bean來源於三級緩存,三級緩存中可能對bean進行了包裝,比如生成了代理對象
                //那么這個地方就需要將 earlySingletonReference 作為最終的bean
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                //回頭看看上面的代碼,剛開始exposedObject=bean,
                // 此時能走到這里,說明exposedObject和bean不一樣了,他們不一樣了說明了什么?
                // 說明initializeBean內部對bean進行了修改
                // allowRawInjectionDespiteWrapping(默認是false):是否允許早期暴露出去的bean(earlySingletonReference)和最終的bean不一致
                // hasDependentBean(beanName):表示有其他bean以利於beanName
                // getDependentBeans(beanName):獲取有哪些bean依賴beanName
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    //判斷dependentBean是否已經被標記為創建了,就是判斷dependentBean是否已經被創建了
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                /**
                 *
                 * 能走到這里,說明早期的bean被別人使用了,而后面程序又將exposedObject做了修改
                 * 也就是說早期創建的bean是A,這個A已經被有些地方使用了,但是A通過initializeBean之后可能變成了B,比如B是A的一個代理對象
                 * 這個時候就坑了,別人已經用到的A和最終容器中創建完成的A不是同一個A對象了,那么使用過程中就可能存在問題了
                 * 比如后期對A做了增強(Aop),而早期別人用到的A並沒有被增強
                 */
                if (!actualDependentBeans.isEmpty()) {
                    //彈出異常(早期給別人的bean和最終容器創建的bean不一致了,彈出異常)
                    throw new BeanCurrentlyInCreationException(beanName,"異常內容見源碼。。。。。");
                }
            }
        }
    }

    return exposedObject;
}

上面的 step1~step4,大家要反復看幾遍**,下面這幾個問題搞清楚之后,才可以繼續向下看,不懂的結合源碼繼續看上面幾個步驟**

1、什么時候 bean 被放入 3 級緩存?

早期的 bean 被放入 3 級緩存

2、什么時候 bean 會被放入 2 級緩存?

當 beanX 還在創建的過程中,此時被加入當前 beanName 創建列表了,但是這個時候 bean 並沒有被創建完畢(bean 被丟到一級緩存才算創建完畢),此時 bean 還是個半成品,這個時候其他 bean 需要用到 beanX,此時會從三級緩存中獲取到 beanX,beanX 會從三級緩存中丟到 2 級緩存中。

3、什么時候 bean 會被放入 1 級緩存?

bean 實例化完畢,初始化完畢,屬性注入完畢,bean 完全組裝完畢之后,才會被丟到 1 級緩存。

4、populateBean 方法是干什么的?

填充屬性的,比如注入依賴的對象。

6.3、下面來看 A、B 類 setter 循環依賴的創建過程

1、getSingleton("a", true) 獲取 a:會依次從 3 個級別的緩存中找 a,此時 3 個級別的緩存中都沒有 a

2、將 a 丟到正在創建的 beanName 列表中(Set<String> singletonsCurrentlyInCreation)

3、實例化 a:A a = new A();這個時候 a 對象是早期的 a,屬於半成品

4、將早期的 a 丟到三級緩存中(Map<String, ObjectFactory<?> > singletonFactories)

5、調用 populateBean 方法,注入依賴的對象,發現 setB 需要注入 b

6、調用 getSingleton("b", true) 獲取 b:會依次從 3 個級別的緩存中找 a,此時 3 個級別的緩存中都沒有 b

7、將 b 丟到正在創建的 beanName 列表中

8、實例化 b:B b = new B();這個時候 b 對象是早期的 b,屬於半成品

9、將早期的 b 丟到三級緩存中(Map<String, ObjectFactory<?> > singletonFactories)

10、調用 populateBean 方法,注入依賴的對象,發現 setA 需要注入 a

11、調用 getSingleton("a", true) 獲取 a:此時 a 會從第 3 級緩存中被移到第 2 級緩存,然后將其返回給 b 使用,此時 a 是個半成品(屬性還未填充完畢)

12、b 通過 setA 將 11 中獲取的 a 注入到 b 中

13、b 被創建完畢,此時 b 會從第 3 級緩存中被移除,然后被丟到 1 級緩存

14、b 返回給 a,然后 b 被通過 A 類中的 setB 注入給 a

15、a 的 populateBean 執行完畢,即:完成屬性填充,到此時 a 已經注入到 b 中了

16、調用a= initializeBean("a", a, mbd)對 a 進行處理,這個內部可能對 a 進行改變,有可能導致 a 和原始的 a 不是同一個對象了

17、調用getSingleton("a", false)獲取 a,注意這個時候第二個參數是 false,這個參數為 false 的時候,只會從前 2 級緩存中嘗試獲取 a,而 a 在步驟 11 中已經被丟到了第 2 級緩存中,所以此時這個可以獲取到 a,這個 a 已經被注入給 b 了

18、此時判斷注入給 b 的 a 和通過initializeBean方法產生的 a 是否是同一個 a,不是同一個,則彈出異常

從上面的過程中我們可以得到一個非常非常重要的結論

當某個 bean 進入到 2 級緩存的時候,說明這個 bean 的早期對象被其他 bean 注入了,也就是說,這個 bean 還是半成品,還未完全創建好的時候,已經被別人拿去使用了,所以必須要有 3 級緩存,2 級緩存中存放的是早期的被別人使用的對象,如果沒有 2 級緩存,是無法判斷這個對象在創建的過程中,是否被別人拿去使用了。

3 級緩存是為了解決一個非常重要的問題:早期被別人拿去使用的 bean 和最終成型的 bean 是否是一個 bean,如果不是同一個,則會產生異常,所以以后面試的時候被問到為什么需要用到 3 級緩存的時候,你只需要這么回答就可以了:三級緩存是為了判斷循環依賴的時候,早期暴露出去已經被別人使用的 bean 和最終的 bean 是否是同一個 bean,如果不是同一個則彈出異常,如果早期的對象沒有被其他 bean 使用,而后期被修改了,不會產生異常,如果沒有三級緩存,是無法判斷是否有循環依賴,且早期的 bean 被循環依賴中的 bean 使用了。

spring 容器默認是不允許早期暴露給別人的 bean 和最終的 bean 不一致的,但是這個配置可以修改,而修改之后存在很大的分享,所以不要去改,通過下面這個變量控制

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#allowRawInjectionDespiteWrapping

private boolean allowRawInjectionDespiteWrapping = false;

6.4、模擬 BeanCurrentlyInCreationException 異常

來個登錄接口 ILogin

package com.javacode2018.cycledependency.demo2;

//登錄接口
public interface ILogin {
}

來 2 個實現類LoginA

這個上面加上@Component注解,且內部需要注入X

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class LoginA implements ILogin {
    @Autowired
    private X x;

    public X getX() {
        return x;
    }

    public void setX(X x) {
        this.x = x;
    }
}

LoginC,不需要 spring 來管理

package com.javacode2018.cycledependency.demo2;

//代理
public class LoginC implements ILogin {

    private ILogin target;

    public LoginC(ILogin target) {
        this.target = target;
    }
}

X 類,有@Component,且需要注入 Ilogin 對象,這個地方會注入 LoginA,此時 LoginA 和 X 會參數循環依賴

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class X {

    @Autowired
    private ILogin login;

    public ILogin getLogin() {
        return login;
    }

    public void setLogin(ILogin login) {
        this.login = login;
    }

}

添加一個 BeanPostProcessor 類,實現postProcessAfterInitialization方法,這個方法發現 bean 是 loginA 的時候,將其包裝為 LoginC 返回,這個方法會在 bean 創建的過程中調用initializeBean時候被調用

package com.javacode2018.cycledependency.demo2;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("loginA")) {
            //loginA實現了ILogin
            return new LoginC((ILogin) bean);
        } else {
            return bean;
        }
    }
}

spring 配置類

package com.javacode2018.cycledependency.demo2;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.refresh();
        context.close();
    }
}

運行輸出,產生了BeanCurrentlyInCreationException異常,是因為注入給 x 的是 LoginA 這個類的對象,而最后容器中 beanname:loginA 對應的是 LoginC 了,導致注入給別人的對象和最終的對象不一致了,產生了異常。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'loginA': Bean with name 'loginA' has been injected into other beans [x] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

7、案例:若只使用 2 級緩存會產生什么后果?

下面來個案例,通過在源碼中設置斷點的方式,來模擬二級緩存產生的后果。

添加 A 類

我們希望 loginA 在 A 類之前被創建好,所以這里用到了@DependsOn 注解。

package com.javacode2018.cycledependency.demo3;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn("loginA") //類A依賴於loginA,但是又不想通過屬性注入的方式強依賴
public class A {

}

接口 ILogin

package com.javacode2018.cycledependency.demo3;

//登錄接口
public interface ILogin {
}

來 2 個實現類,LoginA 需要 spring 管理,LoginC 不需要 spring 管理。

LoginA

package com.javacode2018.cycledependency.demo3;

import org.springframework.stereotype.Component;

@Component
public class LoginA implements ILogin {

}

LoginC

package com.javacode2018.cycledependency.demo3;

//代理
public class LoginC implements ILogin {

    private ILogin target;

    public LoginC(ILogin target) {
        this.target = target;
    }
}

MyBeanPostProcessor

負責將 loginA 包裝為 LoginC

package com.javacode2018.cycledependency.demo3;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("loginA")) {
            //loginA實現了ILogin
            return new LoginC((ILogin) bean);
        } else {
            return bean;
        }
    }
}

啟動類 MainConfig

package com.javacode2018.cycledependency.demo3;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.refresh();
        context.close();
    }
}

下面模擬只使用二級緩存的情況

在 bean 被放到三級緩存之后,下面的一行代碼處設置斷點,操作如下

會彈出一個框,然后填入下面配置,這個配置表示滿足條件的時候,這個斷點才會起效

debug 方式運行程序

走到了這個斷點的位置,此時 loginA 已經被放到第 3 級緩存中,此時如果我們調用this.getSingleton(beanName,true),loginA 會從第 3 級緩存移到第 3 級,這個時候就相當於只有 2 級緩存了,操作如下

點擊下面的按鈕,會彈出一個窗口,可以在窗口中執行代碼,執行this.getSingleton(beanName,true),即將loginA從三級緩存放到 2 級緩存,這樣相當於沒有 3 級緩存了。

 

運行結果,最終也產生了BeanCurrentlyInCreationException異常,實際上這個程序並沒有出現循環依賴的情況,但是如果只用了二級緩存,也出現了早期被暴露的 bean 和最終的 bean 不一致的問題所參數的異常。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException

這個程序如果不進行干預,直接運行,是可以正常運行的,只有在 3 級緩存的情況才可以正常運行。

8、總結

今天的內容有點多,大家慢慢消化,有問題歡迎留言!

9、案例源碼

git地址:
https://gitee.com/javacode2018/spring-series
本文案例對應源碼:
    spring-series\lesson-009-cycledependency

大家 star 一下,所有系列代碼都會在這個里面,還有所有原創文章的連接也在里面,方便查閱!!!

來源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648939328&idx=1&sn=4eecdb72f0cb1206ebbcc8af59886980&scene=21#wechat_redirect


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM