作者 | 波哥
審校 | 重樓
Spring作為當(dāng)前使用最廣泛的框架之一,其重要性不言而喻。所以充分理解Spring的底層實(shí)現(xiàn)原理對(duì)于咱們Java程序員來說至關(guān)重要,那么今天筆者就詳細(xì)說說Spring框架中一個(gè)核心技術(shù)點(diǎn):如何解決循環(huán)依賴問題?
什么是循環(huán)依賴問題?
Spring的循環(huán)依賴問題是指在使用Spring容器管理Bean的依賴關(guān)系時(shí),出現(xiàn)多個(gè)Bean之間相互依賴,形成一個(gè)循環(huán)的依賴關(guān)系。這意味著Bean A 依賴于Bean B,同時(shí)Bean B 也依賴于Bean A,從而形成一個(gè)循環(huán)。Spring容器需要確保這些循環(huán)依賴關(guān)系被正確解決,以避免初始化Bean時(shí)出現(xiàn)問題。
如果你去網(wǎng)上搜索“Spring是如何解決循環(huán)依賴問題的”,絕大部分答案都是:Spring使用三級(jí)緩存確保循環(huán)依賴的解決,包括"singletonObjects"、"earlySingletonObjects"和"singletonFactories"等緩存,以及占位符的使用等等。這當(dāng)然沒有錯(cuò),可是看到這些文章的朋友們,你們真的理解了這其中的原理嗎?還是只是會(huì)背答案呢?那么,今天筆者就來扒一扒Spring是如何解決這一問題的底層實(shí)現(xiàn)原理。當(dāng)然要明白這個(gè)問題的底層實(shí)現(xiàn)原理,你得有一定的Spring源碼基礎(chǔ)才行哦。
現(xiàn)在假設(shè)我們有三個(gè)類,ClasssA、ClassB、ClassC,代碼如下:
下面,我們根據(jù)Spring關(guān)于Bean的生命周期管理過程進(jìn)行分析:
假設(shè)首先實(shí)例化ClassA。我們知道在ClassA實(shí)例化完成后,需要填充屬性classB,在填充classB屬性之前,會(huì)調(diào)用addSingletonFactory方法,把一個(gè)Lambda表達(dá)式添加到了singletonFactories集合中,這個(gè)Lambda表達(dá)式的代碼如下:
在填充屬性時(shí),需要獲取到classB的實(shí)例對(duì)象,也就是說會(huì)調(diào)用getBean("classB")來走classB這個(gè)bean實(shí)例的生命周期流程。
在獲取classB實(shí)例時(shí),首先會(huì)調(diào)用getSingleton從singletonObjects獲?。ǘ@個(gè)singletonObjects就是我們平常所說的單例池, 其實(shí)就是個(gè)map集合):
如果單例池中沒有才會(huì)去創(chuàng)建,那么此時(shí)單例池中肯定沒有ClassB的實(shí)例,所以針對(duì)classB實(shí)例也會(huì)走一遍創(chuàng)建實(shí)例的生命周期的流程,同樣的也會(huì)把上述Lambda表達(dá)式添加到singletonFactories集合中。
此時(shí)singletonFactories集合中就有了classA和classB的兩個(gè)表達(dá)式。
但是這里我們要特別注意classB中需要填充屬性classA,所以在填充classB實(shí)例的classA屬性時(shí),同樣需要調(diào)用getBean("classA")方法來獲取到classA的實(shí)例,在獲取classA實(shí)例時(shí),同樣首先會(huì)調(diào)用getSingleton從單例池中獲?。?/span>
如代碼所示,首先會(huì)根據(jù)beanName從singletonObjects獲取,也就是獲取classA,很顯然,classA還沒有放到單例池里面去,只有完全創(chuàng)建好的實(shí)例才會(huì)放到單例池里面去??梢钥吹酱a同時(shí)執(zhí)行
isSingletonCurrentlyInCreation,此時(shí)這個(gè)方法返回的是true,內(nèi)容如下:
那這個(gè)isSingletonCurrentlyInCreation方法是干嘛用的呢?看方法名字就知道了,就是判斷當(dāng)前這個(gè)bean是否正在創(chuàng)建中,我們?cè)陂_始創(chuàng)建classA的時(shí)候就已經(jīng)把他的名字添加到singletonsCurrentlyInCreation這個(gè)集合中,表明正在創(chuàng)建classA。
很顯然滿足了if (singletonObject == null &&isSingletonCurrentlyInCreation(beanName))這個(gè)條件,于是就進(jìn)入到if的方法體中。
然后從earlySingletonObjects這個(gè)集合中獲取對(duì)象,那這個(gè)earlySingletonObjects又是個(gè)啥玩意?只用singletonFactories和singletonObjects兩個(gè)緩存集合不就好了嗎?還要多此一舉使用earlySingletonObjects干啥呢?是不是感覺沒什么用?千萬(wàn)別這么看,大師們考慮問題比咱們要考慮的周到,不服都不行。
我們這個(gè)案例中ClassA依賴ClassB和ClassC,ClassB依賴ClassA,ClassC也依賴ClassA,假如我們沒有這個(gè)earlySingletonObjects會(huì)出現(xiàn)什么情況呢?我們調(diào)用singletonFactories.get(beanName)得到前面說的classA的那個(gè)Lambda表達(dá)式,然后執(zhí)行
singletonFactory.getObject()就開始執(zhí)行這個(gè)Lambda表達(dá)式,在填充ClassB中的classA屬性時(shí)是不是相當(dāng)于執(zhí)行了這個(gè)Lambda表達(dá)式獲取了這個(gè)classA對(duì)象。
好了,到此為止classA中的classB屬性獲取到了,接下來填充classC了,上述同樣的流程,當(dāng)填充classC的classA屬性時(shí),是不是還得從singletonFactories中獲取classA的Lambda表達(dá)式,然后再執(zhí)行那個(gè)Lambda表達(dá)式,于是執(zhí)行了兩次,正常情況下是沒有問題的,因?yàn)閮蓚€(gè)Lambda表達(dá)式返回的結(jié)果都是classA的實(shí)例對(duì)象,但是有一種情況下就會(huì)有問題了?老鐵們此時(shí)心中肯定充滿疑惑,神馬情況呢?
如果執(zhí)行這個(gè)Lambda表達(dá)式返回的是classA的代理對(duì)象呢?如果執(zhí)行了兩次,是不是就表明classB中的classA屬性和classC中的classA屬性是兩個(gè)不同的對(duì)象了?這問題可就大了,那么問題又來了,神馬情況下會(huì)返回classA的代理對(duì)象?不賣關(guān)子了,直接上答案:在classA需要AOP的情況下,是需要生成代理對(duì)象的,而這個(gè)生成AOP的騷操作就是在這個(gè)Lambda表達(dá)式中實(shí)現(xiàn)的,我們下面會(huì)詳細(xì)介紹。
所以這里Spring使用了earlySingletonObjects這個(gè)我們稱為二級(jí)緩存的集合來暫存下,這樣在classC填充classA屬性的時(shí)候就不用再次調(diào)用lambda表達(dá)式了,是不是完美的解決了上述的問題?剩下的幾行代碼很簡(jiǎn)單,就不多廢話了,大家自己看看就知道了。
總結(jié)下,Spring解決循環(huán)依賴問題其實(shí)就是使用了幾個(gè)集合類,它們分別是:singletonsCurrentlyInCreation(Set)、singletonFactories(Map)、earlySingletonObjects(Map)、singletonObjects(Map),通過這幾個(gè)集合的相互配合,最終解決循環(huán)依賴問題。
作者介紹
波哥,互聯(lián)行業(yè)從業(yè)10余年,先后擔(dān)任項(xiàng)目總監(jiān)及架構(gòu)師。目前專攻技術(shù),喜歡研究技術(shù)原理。技術(shù)全面,主攻Java,精通JVM底層機(jī)制及Spring全家桶底層框架原理,熟練掌握當(dāng)前主流的中間件、服務(wù)網(wǎng)格等技術(shù)原理。