靈魂畫手圖解 Spring 循環(huán)依賴
(給 java金融 加星標(biāo),提高Java技能)
前言
想徹底弄清楚 Spring 的循環(huán)依賴問題,首先得弄清楚,
-
循環(huán)依賴是如何發(fā)生的,Spring 又是如何檢測循環(huán)依賴的發(fā)生的。
-
其次再探究 Spring 如何解決循環(huán)依賴的問題。
最后,我們將總結(jié)循環(huán)依賴解決的兩個關(guān)鍵因素,提前曝光和曝光時機,缺一不可。
1、循環(huán)依賴檢查
<bean id="a" class="A"><property name="b" ref="b"><bean/><bean id="b" class="B"><property name="a" ref="a"><bean/>
無論單例還是原型模式 (下文①代表圖中步驟 1),Spring 都有對應(yīng)的集合保存當(dāng)前正在創(chuàng)建的 beanName,標(biāo)識該 beanName 正在被創(chuàng)建。在 bean 創(chuàng)建前,①檢測當(dāng)前 bean 是否在創(chuàng)建中,如果不在創(chuàng)建中則②將 beanName 加入集合,往下創(chuàng)建 bean。在 bean 創(chuàng)建前,檢測到當(dāng)前的 bean 正在創(chuàng)建,則說明發(fā)生循環(huán)依賴,拋出異常。最后記得當(dāng) bean 創(chuàng)建完時將 beanName 移出集合。

2、循環(huán)依賴的處理
單例 setter 循環(huán)依賴
Spring 注入屬性的方式有多種,但是只有一種循環(huán)依賴能被解決:setter 依賴注入。前面或多或少都提到了 Spring 解決循環(huán)依賴的做法是未等 bean 創(chuàng)建完就先將實例曝光出去,方便其他 bean 的引用。同時還提到了三級緩存,最先曝光到第三級緩存 singletonFactories 中。簡單的說,就是 Spring 先將創(chuàng)建好的實例放到緩存中,讓其他 bean 可以提前引用到該對象。
示例
// 第一種 注解方式public class A {@Autowiredprivate B b;}
public class B {@Autowiredprivate A a;}
// ===========================// 第二種 xml配置方式public class A {private B b;// getter setter}
public class B {private A a;// getter setter}
<bean id="a" class="A"><property name="b" ref="b"><bean/><bean id="b" class="B"><property name="a" ref="a"><bean/>
分析

上圖我覺得我畫的很滿意,堪稱靈魂畫手。其中跟循環(huán)依賴檢測對比,新添加的幾個關(guān)鍵節(jié)點已經(jīng)用黃色標(biāo)識出來,這里有幾個重點給大家畫一下。
-
提前曝光,如果用 C 語言的說法就是將指針曝光出去,用 java 就是將引用對象曝光出去。也就是說即便 a 對象還未創(chuàng)建完成,但是在④實例化過程中 new A() 動作已經(jīng)開辟了一塊內(nèi)存空間,只需要將該地址拋出去 b 就可以引用的到,而不管 a 后期還會進行初始化等其他操作;
-
已經(jīng)了解了提前曝光的作用,而相比而言⑤曝光的時機也非常的重要,該時機發(fā)生在④實例化之后,⑥填充與? 初始化之前。Spring 循環(huán)依賴之所以不能解決實例化注入的原因正式因為注入時機在曝光之前所導(dǎo)致;
-
⑤中寫的帶 a 的工廠是什么東西?先來了解一下 ObjectFatory。
public interface ObjectFactory<T> {T getObject() throws BeansException;}
就是一個接口,通過重寫 getObject() 方法返回對應(yīng)的 object。
// 將該bean提前曝光,具體做法是創(chuàng)建一個ObjectFactory對象,再將對象加入到singletonFactories緩存中addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
讓我?guī)痛蠹腋膶懸幌拢蝗豢赡芸戳擞悬c懵逼,以上代碼等同于:
addSingletonFactory(beanName, new ObjectFactory<Object>() {@Overridepublic Object getObject() throws BeansException {getEarlyBeanReference(beanName, mbd, bean);}});
但是我們看到,按原計劃重寫 getObject() 應(yīng)該是直接 return bean 就行了,為什么還有 getEarlyBeanReference 是什么鬼?(這點非常重要,但是我看了很多博客甚至?xí)径纪耆鲆暳诉@點,如果忽視了這點,那三級緩存將失去意義,直接二級緩存就可以解決提前曝光的問題)
getEarlyBeanReference 目的就是為了后置處理,給一個在提前曝光時操作 bean 的機會,具體要怎么操作 bean,那就繼承 SmartInstantiationAwareBeanPostProcessor 重寫 getEarlyBeanReference 方法吧。比如你要 System。out。print (“啊啊啊啊,我是” + bean + “,我被曝光且提前引用啦”) 也是可以的,關(guān)鍵就在于 bean 被曝光到三級緩存時并沒用使用提前曝光的后置處理,而是當(dāng)三級緩存被提前引用到二級緩存時才觸發(fā)!(但是在 Spring 的源碼中,真正實現(xiàn)這個方法的只有 AbstractAutoProxyCreator 這個類,用于提前曝光的 AOP 代理。
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;// 這么一大段就這句話是核心,也就是當(dāng)bean要進行提前曝光時,// 給一個機會,通過重寫后置處理器的getEarlyBeanReference方法,來自定義操作bean// 值得注意的是,如果提前曝光了,但是沒有被提前引用,則該后置處理器并不生效!!!// 這也正式三級緩存存在的意義,否則二級緩存就可以解決循環(huán)依賴的問題exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
單例構(gòu)造器注入循環(huán)依賴
上面已經(jīng)劇透了這個方式是不行的,原因是依賴注入的時間點不對,他的依賴注入發(fā)生在構(gòu)造器階段,這個時候連實例都沒有,內(nèi)存都還沒開辟完,當(dāng)然也還沒有進行提前曝光,因此不行。
示例
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}}
分析

圖上重點地方也用黃色標(biāo)出了,問題的原因處在④實例化,實例化的過程是調(diào)用 new A (B b); 的過程,這時的 A 還未創(chuàng)建出來,根本是不可能提前曝光的,正是這個原因?qū)е垄釤o法獲取到三級緩存,進而導(dǎo)致⑩異常的拋出。
原型模式循環(huán)依賴
這此沒有圖了,因為原型模式每次都是重新生成一個全新的 bean,根本沒有緩存一說。這將導(dǎo)致實例化 A 完,填充發(fā)現(xiàn)需要 B,實例化 B 完又發(fā)現(xiàn)需要 A,而每次的 A 又都要不一樣,所以死循環(huán)的依賴下去。唯一的做法就是利用循環(huán)依賴檢測,發(fā)現(xiàn)原型模式下存在循環(huán)依賴并拋出異常。
總結(jié)
總結(jié)一下循環(huán)依賴,Spring 只能解決 setter 注入單例模式下的循環(huán)依賴問題。要想解決循環(huán)依賴必須要滿足兩個條件:
- 需要用于提前曝光的緩存;
- 屬性的注入時機必須發(fā)生在提前曝光動作之后,不管是填充還是初始化都行,總之不能在實例化,因為提前曝光動作在實例化之后。
理解了這兩點就可以輕松駕馭循環(huán)依賴了。比如構(gòu)造器注入是不滿足第二個條件,曝光時間不對。而原型模式則是缺少了第一個條件,沒有提前曝光的緩存供使用。
轉(zhuǎn)自:bugpool,
鏈接:blog.csdn.net/chaitoudaren/article/details/104833575
- EOF -
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
關(guān)注「java金融」,提升Java技能
點贊和在看就是最大的支持 ??
