什么是循環(huán)依賴?
舉個(gè)例子
/**
* A 類,引入 B 類的屬性 b
*/
public class A {
private B b;
}
/**
* B 類,引入 A 類的屬性 a
*/
public class B {
private A a;
}
再看個(gè)簡(jiǎn)單的圖:
像這樣,創(chuàng)建 a 的時(shí)候需要依賴 b,那就創(chuàng)建 b,結(jié)果創(chuàng)建 b 的時(shí)候又需要依賴 a,那就創(chuàng)建 a,創(chuàng)建 a 的時(shí)候需要依賴 b,那就創(chuàng)建 b,結(jié)果創(chuàng)建 b 的時(shí)候又需要依賴 a ……
互相依賴何時(shí)了,死循環(huán)了吧? 諾,這就是循環(huán)依賴!
循環(huán)依賴其實(shí)不算個(gè)問(wèn)題或者錯(cuò)誤,我們實(shí)際在開(kāi)發(fā)的時(shí)候,也可能會(huì)用到。 再拿最開(kāi)始的 A 和 B 來(lái)說(shuō),我們手動(dòng)使用的時(shí)候會(huì)用以下方式:
A a = new A();
B b = new B();
b.setA(a);
a.setB(b);
其實(shí)這樣就解決了循環(huán)依賴,功能上是沒(méi)有問(wèn)題的,但是為什么 Spring 要解決循環(huán)依賴?
為什么 Spring 要解決循環(huán)依賴?
首先簡(jiǎn)單了解下,我們用 Spring 框架,它幫我們做了什么事情?總結(jié)上來(lái)說(shuō),六字真言:IoC 和 AOP。
由于 Spring 解決循環(huán)依賴是考慮到 IoC 和 AOP 相關(guān)知識(shí)了,所以這里我先提一下。
由于本文主要的核心是 Spring 的循環(huán)依賴處理,所以不會(huì)對(duì) IoC 和 AOP 做詳細(xì)的說(shuō)明,想了解以后有機(jī)會(huì)再說(shuō)
IoC,主要是將對(duì)象的創(chuàng)建、管理都交給了 Spring 來(lái)管理,能夠解決對(duì)象之間的耦合問(wèn)題,對(duì)開(kāi)發(fā)人員來(lái)說(shuō)也是省時(shí)省力的。
AOP,主要是在不改變?cè)袠I(yè)務(wù)邏輯情況下,增強(qiáng)橫切邏輯代碼,也是解耦合,避免橫切邏輯代碼重復(fù);也是對(duì) OOP 的延續(xù)、補(bǔ)充。
既然類的實(shí)例化都交給了 Spring 來(lái)管理了,那么循環(huán)依賴 Spring 肯定也要考慮到怎么去處理(怎么總覺(jué)得有點(diǎn)像是廢話 )。
解決循環(huán)依賴的方式
參考我們能想到的肯定是手動(dòng)處理的方式,先將對(duì)象都 new 出來(lái),然后進(jìn)行 set 屬性值,而 Spring 也是通過(guò)這樣的形式來(lái)處理的(你說(shuō)巧不巧?其實(shí)一點(diǎn)都不巧 ,后面再說(shuō)為什么),其實(shí) Spring 管理 Bean 的實(shí)例化底層其實(shí)是由反射實(shí)現(xiàn)的。
而我們實(shí)例化的方式也有好多種,比如通過(guò)構(gòu)造函數(shù),一次性將屬性賦值,像下面這樣
// 假設(shè)有學(xué)生這個(gè)類
public class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
// 通過(guò)構(gòu)造器方式實(shí)例化并賦值
new Student(1, "Suremotoo");
但是使用構(gòu)造器這樣的方式,是無(wú)法解決循環(huán)依賴的!為什么不能呢?
我們還是以文中開(kāi)頭的 A 和 B 互相依賴來(lái)說(shuō), 要通過(guò)構(gòu)造器的方式實(shí)現(xiàn) A 的實(shí)例化,如下
new A(b);
Wow,是不是發(fā)現(xiàn)問(wèn)題了?要通過(guò)構(gòu)造器的方式,首先要將屬性值實(shí)例化出來(lái)啊!A 要依賴屬性 b,就需要先將 B 實(shí)例化,可是 B 的實(shí)例化是不是還是需要依賴 A?? 這不就是文中開(kāi)頭描述的樣子嘛,所以通過(guò)構(gòu)造器的方式,Spring 也沒(méi)有辦法解決循環(huán)依賴。
我們使用 set 可以解決,那么 Spring 也使用 set 方式呢?答案是可以的。
既然底層是通過(guò)反射實(shí)現(xiàn)的,我們自己也用反射實(shí)現(xiàn)的話,大概思路是這樣的(還是以 A 和 B 為例)
- 先實(shí)例化 A 類
- 再實(shí)例化 B 類
- set B 類中的 a 屬性
- set A 類中的 b 屬性
其實(shí)就是通過(guò)反射,實(shí)現(xiàn)以下代碼
A a = new A();
B b = new B();
b.setA(a);
a.setB(b);
這里可以稍微說(shuō)明一下,為什么這樣可以?
A a = new A(),說(shuō)明 A 只是實(shí)例化,還未初始化
同理,B b = new B() 也只是實(shí)例化,并未初始化
a.setB(b);, 對(duì) a 的屬性賦值,完成 a 的初始化
b.setA(a);, 對(duì) b 的屬性賦值,完成 b 的初始化
現(xiàn)在是不是有點(diǎn)感覺(jué)了,先把狗騙進(jìn)來(lái),再殺
Spring 如何解決循環(huán)依賴問(wèn)題
先上個(gè)通俗的答案解釋,三級(jí)緩存。
/**
* 單例對(duì)象的緩存:bean 名稱——bean 實(shí)例,即:所謂的單例池。
* 表示已經(jīng)經(jīng)歷了完整生命周期的 Bean 對(duì)象
* <b>第一級(jí)緩存</b>
*/
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 早期的單例對(duì)象的高速緩存:bean 名稱——bean 實(shí)例。
* 表示 Bean 的生命周期還沒(méi)走完(Bean 的屬性還未填充)就把這個(gè) Bean 存入該緩存中
* 也就是實(shí)例化但未初始化的 bean 放入該緩存里
* <b>第二級(jí)緩存</b>
*/
Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* 單例工廠的高速緩存:bean 名稱——ObjectFactory。
* 表示存放生成 bean 的工廠
* <b>第三級(jí)緩存</b>
*/
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
代碼中注釋可能不清晰,我再給貼一下三級(jí)緩存:
第一級(jí)緩存(也叫單例池):Map<String, Object> singletonObjects,存放已經(jīng)經(jīng)歷了完整生命周期的 Bean 對(duì)象
第二級(jí)緩存:Map<String, Object> earlySingletonObjects,存放早期暴露出來(lái)的 Bean 對(duì)象,Bean 的生命周期未結(jié)束(屬性還未填充完)
第三級(jí)緩存:Map<String, ObjectFactory<?>> singletonFactories,存放可以生成 Bean 的工廠
Spring 管理的 Bean 其實(shí)默認(rèn)都是單例的,也就是說(shuō) Spring 將最終可以使用的 Bean 統(tǒng)一放入第一級(jí)緩存中,也就是 singletonObjects(單例池)里,以后凡是用到某個(gè) Bean 了都從這里獲取就行了。
僅使用一級(jí)緩存可以嗎?
既然都從 singletonObjects 里獲取,那么僅僅使用這一個(gè) singletonObjects,可以嗎?肯定不可以的。 首先 singletonObjects 存入的是完全初始化好的 Bean,可以拿來(lái)直接用的。 如果我們直接將未初始化完的 Bean 放在 singletonObjects 里面,注意,這個(gè)未初始化完的 Bean 極有可能會(huì)被其他的類拿去用,它都沒(méi)完事呢,就被拿去造了,肯定要出事??!
我們以 A 、 B、C 舉例子
- 先實(shí)例化 A 類,叫 a
- 將 a 放入 singletonObjects 中(此時(shí) a 中的 b 屬性還是空的呢)
- C 類需要使用 A 類,去 singletonObjects 獲取,且獲取到了 a
- C 類使用 a,拿出 a 類的 b 屬性,然后 NPE了.
諾,出事了吧,這下就不是解決循環(huán)依賴的問(wèn)題了,反而設(shè)計(jì)就不對(duì)了。
NPE 就是 NullPointerException
使用二級(jí)緩存可以嗎?
再來(lái)回顧下循環(huán)依賴的問(wèn)題:A→B→A→B……
說(shuō)到底就是怎么打破這個(gè)循環(huán),一級(jí)緩存不行,我們就再加一級(jí),可以嗎? 我們看個(gè)圖
圖中的緩存就是二級(jí)緩存
看完圖,可能還會(huì)有疑惑,A 沒(méi)初始化完成放入了緩存,那么 B 用的豈不是就是未完成的 A,是這樣的沒(méi)錯(cuò)! 在整個(gè)過(guò)程當(dāng)中,A 是只有 1 個(gè),而 B 那里的 A 只是 A 的引用,所以后面 A 完成了初始化,B 中的 A 自然也就完成了。這里就是文中前面提到的手動(dòng) setA,setB 那里,我再貼一下代碼:
A a = new A();
B b = new B();
b.setA(a); // 這里設(shè)置 b 的屬性 a,其實(shí)就是 a 的引用
a.setB(b); // 這里設(shè)置 a 的屬性 b,此時(shí)的 b 已經(jīng)完成了初始化,設(shè)置完 a 的屬性, a 也就完成了初始化,那么對(duì)應(yīng)的 b 也就完成了初始化
分析到這里呢,我們就會(huì)發(fā)現(xiàn)二級(jí)緩存就解決了循環(huán)依賴的問(wèn)題了,可是為什么還要三級(jí)緩存呢? 這里就要說(shuō)說(shuō) Spring 中 Bean 的生命周期。
Spring 中 Bean 的管理

要明白 Spring 中的循環(huán)依賴,首先得了解下 Spring 中 Bean 的生命周期。
被 Spring 管理的對(duì)象叫 Bean 這里不會(huì)對(duì) Bean 的生命周期進(jìn)行詳細(xì)的描述,只是描述一下大概的過(guò)程,方便大家去理解循環(huán)依賴。
Spring 中 Bean 的生命周期,指的就是 Bean 從創(chuàng)建到銷毀的一系列生命活動(dòng)。
那么由 Spring 來(lái)管理 Bean,要經(jīng)過(guò)的主要步驟有:
- Spring 根據(jù)開(kāi)發(fā)人員的配置,掃描哪些類由 Spring 來(lái)管理,并為每個(gè)類生成一個(gè) BeanDefintion,里面封裝了類的一些信息,如全限定類名、哪些屬性、是否單例等等
- 根據(jù) BeanDefintion 的信息,通過(guò)反射,去實(shí)例化 Bean(此時(shí)就是實(shí)例化但未初始化 的 Bean)
- 填充上述未初始化對(duì)象中的屬性(依賴注入)
- 如果上述未初始化對(duì)象中的方法被 AOP 了,那么就需要生成代理類(也叫包裝類)
- 最后將完成初始化的對(duì)象存入緩存中(此處緩存 Spring 里叫: singletonObjects),下次用從緩存獲取 ok 了
如果沒(méi)有涉及到 AOP,那么第四步就沒(méi)有生成代理類,將第三步完成屬性填充的對(duì)象存入緩存中。
二級(jí)緩存有什么問(wèn)題?
如果 Bean 沒(méi)有 AOP,那么用二級(jí)緩存其實(shí)沒(méi)有什么問(wèn)題的,一旦有上述生命周期中第四步,就會(huì)導(dǎo)致的一個(gè)問(wèn)題。因?yàn)?AOP 處理后,往往是需要生成代理對(duì)象的,代理對(duì)象和原來(lái)的對(duì)象根本就不是 1 個(gè)對(duì)象。
以二級(jí)緩存的場(chǎng)景來(lái)說(shuō),假設(shè) A 類的某個(gè)方法會(huì)被 AOP,過(guò)程就是這樣的:
- 生成 a 的實(shí)例,然后放入緩存,a 需要 b
- 再生成 b ,填充 b 的時(shí)候,需要 a,從緩存中取到了 a,完成 b 的初始化;
- 緊接著 a 把初始化好的 b 拿過(guò)來(lái)用,完成 a 的屬性填充和初始化
- 由于 A 類涉及到了 AOP,再然后 a 要生成一個(gè)代理類,這里就叫:代理 a 吧
結(jié)果就是:a 最終的產(chǎn)物是代理 a,那 b 中其實(shí)也應(yīng)該用代理 a,而現(xiàn)在 b 中用的卻是原始的 a 代理 a 和原始的 a 不是一個(gè)對(duì)象,現(xiàn)在這就有問(wèn)題了。
使用三級(jí)緩存如何解決?
二級(jí)緩存還是有問(wèn)題,那就再加一層緩存,也就是第三級(jí)緩存:Map<String, ObjectFactory<?>> singletonFactories,在 bean 的生命周期中,創(chuàng)建完對(duì)象之后,就會(huì)構(gòu)造一個(gè)這個(gè)對(duì)象對(duì)應(yīng)的 ObjectFactory 存入 singletonFactories 中。
singletonFactories 中存的是某個(gè) beanName 及對(duì)應(yīng)的 ObjectFactory,這個(gè) ObjectFactory 其實(shí)就是生成這個(gè) Bean 的工廠。實(shí)際中,這個(gè) ObjectFactory 是個(gè) Lambda 表達(dá)式:() -> getEarlyBeanReference(beanName, mbd, bean),而且,這個(gè)表達(dá)式并沒(méi)有執(zhí)行。
那么 getEarlyBeanReference 具體做了什么事情?
核心就是兩步:
第一步:根據(jù) beanName 將它對(duì)應(yīng)的實(shí)例化后且未初始化完的 Bean,存入 Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
第二步:生成該 Bean 對(duì)應(yīng)的代理類返回
這個(gè) earlyProxyReferences其實(shí)就是用于記錄哪些 Bean 執(zhí)行過(guò) AOP,防止后期再次對(duì) Bean 進(jìn)行 AOP
那么 getEarlyBeanReference 什么時(shí)候被觸發(fā),什么時(shí)候執(zhí)行?
在二級(jí)緩存示例中,填充 B 的屬性時(shí)候,需要 A,然后去緩存中拿 A,此時(shí)先去第三級(jí)緩存中去取 A,如果存在,此時(shí)就執(zhí)行 getEarlyBeanReference 函數(shù),然后該函數(shù)就會(huì)返回 A 對(duì)應(yīng)的代理對(duì)象。
后續(xù)再將該代理對(duì)象放入第二級(jí)緩存中,也就是 Map<String, Object> earlySingletonObjects里。
為什么不放入第一級(jí)緩存呢?
此時(shí)就拿到的代理對(duì)象,也是未填充屬性的,也就是仍然是未初始化完的對(duì)象。
如果直接放入第一級(jí)緩存,此時(shí)被其他類拿去使用,肯定有問(wèn)題了。
那么什么時(shí)候放入第一級(jí)緩存?
這里需要再簡(jiǎn)單說(shuō)下第二級(jí)緩存的作用,假如 A 經(jīng)過(guò)第三級(jí)緩存,獲得代理對(duì)象,這個(gè)代理對(duì)象仍然是未初始化完的!那么就暫時(shí)把這個(gè)代理對(duì)象放入第二級(jí)緩存,然后刪除該代理對(duì)象原本在第三級(jí)緩存中的數(shù)據(jù)(確保后期不會(huì)每次都生成新的代理對(duì)象),后面其他類要用了 A,就去第二級(jí)緩存中找,就獲取到了 A 的代理對(duì)象,而且都用的是同一個(gè) A 的代理對(duì)象,這樣后面只需要對(duì)這一個(gè)代理對(duì)象進(jìn)行完善,其他引入該代理對(duì)象的類就都完善了。
再往后面,繼續(xù)完成 A 的初始化,那么先判斷 A 是否存在于 earlyProxyReferences 中, 存在就說(shuō)明 A 已經(jīng)經(jīng)歷過(guò) AOP 了,就無(wú)須再次 AOP。那 A 的操作就轉(zhuǎn)換從二級(jí)緩存中獲取,把 A 的代理類拿出來(lái),填充代理類的屬性。
完成后再將 A 的代理對(duì)象加入到第一級(jí)緩存,再把它原本在第二級(jí)緩存中的數(shù)據(jù)刪掉,確保后面還用到 A 地類,直接從第一級(jí)緩存中獲取。
看個(gè)圖理解下

總結(jié)
說(shuō)了這么多,總結(jié)下三級(jí)緩存:
第一級(jí)緩存(也叫單例池):Map<String, Object> singletonObjects,存放已經(jīng)經(jīng)歷了完整生命周期的 Bean 對(duì)象
第二級(jí)緩存:Map<String, Object> earlySingletonObjects,存放早期暴露出來(lái)的 Bean 對(duì)象,Bean 的生命周期未結(jié)束(屬性還未填充完),可能是代理對(duì)象,也可能是原始對(duì)象
第三級(jí)緩存:Map<String, ObjectFactory<?>> singletonFactories,存放可以生成 Bean 的工廠,工廠主要用來(lái)生成 Bean 的代理對(duì)象
附: 一個(gè)完整的 Spring 循環(huán)依賴時(shí)序圖
時(shí)序圖并不標(biāo)準(zhǔn),但是方便大家去理解 在理解循環(huán)依賴的時(shí)候,整體是個(gè)遞歸,你要有種套中套、夢(mèng)中夢(mèng)的感覺(jué)
作者:讓我來(lái)處理高并發(fā)
鏈接:http://m.itdecent.cn/p/5097fde06d50
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。