今天來聊一個面試中經常會被問到的問題,咱們一起必須把這個問題搞懂。
問題: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