詳解Spring如何解決循環(huán)引用的問(wèn)題
Spring如何解決循環(huán)引用的問(wèn)題
關(guān)于循環(huán)引用,首先說(shuō)一個(gè)結(jié)論:
Spring能夠解決的情況為:兩個(gè)對(duì)象都是單實(shí)例、且通過(guò)set方法進(jìn)行注入。
兩個(gè)對(duì)象都是單實(shí)例,通過(guò)構(gòu)造方法進(jìn)行注入,Spring不能進(jìn)行循環(huán)引用問(wèn)題;
兩個(gè)對(duì)象都是多實(shí)例的情況下,不管是set注入,還是構(gòu)造注入,都不能解決Spring循環(huán)引用問(wèn)題。
循環(huán)引用問(wèn)題介紹
循環(huán)引用問(wèn)題即:
有A,B兩個(gè)類,A類中有B類型的成員變量b、B類中有A類型的成員變量a。創(chuàng)建a的過(guò)程需要b,創(chuàng)建b的過(guò)程又需要a;
循環(huán)引用問(wèn)題分析
請(qǐng)看如下流程:
- 調(diào)用getBean("a")來(lái)獲取a對(duì)象;
- 先調(diào)用getSingleton("a")來(lái)嘗試獲取a,但是獲取不到;
- 需要調(diào)用doCreateBean()來(lái)創(chuàng)建a;
- a的b屬性是null,需要填充b屬性;
- 調(diào)用getBean("b")來(lái)獲取b對(duì)象;
- 先調(diào)用getSingleton("b")來(lái)嘗試獲取b,但是獲取不到;
- 需要調(diào)用doCreateBean()來(lái)創(chuàng)建b;
- b的a屬性是null,需要填充a屬性;
- 又需要要調(diào)用getBean("a")來(lái)獲取a。
這時(shí)getBean("a")可以獲取到嗎?如果能獲取到,是在哪里獲取的?如果獲取不到,又會(huì)有什么問(wèn)題呢?
我們首先看下getSingleton()源碼:
addSingleton方法如下圖:
如此可以看到,在進(jìn)行實(shí)例化、屬性填充、初始化都完成后才會(huì)放到singletonObjects中。
那getSingleton()方法就獲取不到a,只能再去創(chuàng)建a對(duì)象了嗎?當(dāng)然不是,如果再去創(chuàng)建a,a就不是單例的呢。
所以這就需要**沒(méi)有創(chuàng)建完全的a也要存儲(chǔ)起來(lái)。**但是并沒(méi)有存儲(chǔ)到singletonObjects中,因?yàn)閟ingletonObjects是存儲(chǔ)例化、屬性填充、初始化都完成后的對(duì)象。
Spring又為我們定義了兩個(gè)存儲(chǔ)的位置:earlySingletonObjects、singletonFactories。
那什么時(shí)候?qū)⑽磩?chuàng)建完全的對(duì)象存儲(chǔ)起來(lái)呢?
這我們應(yīng)該在實(shí)例化對(duì)象完成后,填充屬性前的代碼查找??梢钥吹饺缦麓a:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); addSingletonFactory方法源碼如下: protected void addSingletonFactory(String beanName, ObjectFactory <? > singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized(this.singletonObjects) { if(!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
實(shí)例化后,會(huì)把創(chuàng)建非完全體對(duì)象的工廠放到singletonFactories里,這個(gè)工廠就是lambda表達(dá)式() -> getEarlyBeanReference(beanName, mbd, bean)調(diào)用的getEarlyBeanReference(beanName, mbd, bean)方法。
addSingletonFactory還會(huì)把earlySingletonObjects、registeredSingletons中的對(duì)象刪除。
singletonFactories 存儲(chǔ):不完全體的bean的id作為key,一個(gè)工廠作為value; 工廠方法是lambda表達(dá)式()->getEarlyBeanReference(beanName, mbd, bean) 此方法內(nèi)部使用了BeanPostProcessor。
singletonFactories為什么不存儲(chǔ)未完全體的a,而存儲(chǔ)一個(gè)工廠方法呢?
這意味著他會(huì)處理一些復(fù)雜功能。
上述介紹的循環(huán)引用的問(wèn)題,是最簡(jiǎn)單的情況。還有一些復(fù)雜情況。
如果A需要做AOP,需要為A做代理呢?或者B也要做代理呢?
代理是在初始化階段使用BeanPostProcessor的postProcessAfterInitialization()方法來(lái)做的。
singletonFactories存工廠的原因:
為b填充屬性a時(shí),需要獲取到不完全體的a,為b賦值; 并且如果A需要做代理; 而代理是在BeanPostProcessor中的postProcessAfterInitialization()方法做的; 所以singletonFactories存儲(chǔ)的是一個(gè)工廠(里面的方法是用BeanPostProcessor中的); 這樣就無(wú)需在a初始化的過(guò)程中創(chuàng)建代理了,可以把a(bǔ)的代理提前創(chuàng)建出來(lái)。
那在A創(chuàng)建過(guò)程中是否還要?jiǎng)?chuàng)建代理呢?————不會(huì)。
在上面提前創(chuàng)建a的代理完成后,會(huì)將代理對(duì)象放到代理緩存中,在a初始化創(chuàng)建代理時(shí),直接從代理緩存中拿就可以了。
站在b的角度講,現(xiàn)在b的屬性填充完成了,后面就是初始化了,在初始化過(guò)程中,就可以走正常的代理過(guò)程了。
a在填充屬性時(shí),就可以填充b的代理了,就可以走初始化了,初始化過(guò)程中的代理從代理緩存獲取就可以了。
為b填充a代理對(duì)象分析
doGetBean()中的getSingleton方法:
在為b填充a的代理時(shí),singletonFactory.getObject()就會(huì)回調(diào)存儲(chǔ)起來(lái)的那個(gè)lambda表達(dá)式()->getEarlyBeanReference(beanName, mbd, bean)。
會(huì)把a(bǔ)的代理獲取出來(lái);
然后把a(bǔ)的代理放到earlySingletonObjects中;
把存儲(chǔ)的a工廠的lambda表達(dá)式從singletonFactories中移除。
b初始化完成后,b就是完全體了,調(diào)用addSingleton()方法就會(huì)把b存儲(chǔ)到singletonObjects中了。
等a再初始化完成就是完全體了。
這樣就解決了循環(huán)引用問(wèn)題。
以上就是詳解Spring如何解決循環(huán)引用的問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Spring循環(huán)引用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java was started but returned exit code=13問(wèn)題解決案例詳解
這篇文章主要介紹了Java was started but returned exit code=13問(wèn)題解決案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09通過(guò)Java實(shí)現(xiàn)RSA加密與驗(yàn)證的方法詳解
RSA是一種非對(duì)稱加密算法,是目前廣泛應(yīng)用于加密和數(shù)字簽名領(lǐng)域的一種加密算法,本文主要講述如何通過(guò)Java實(shí)現(xiàn)RSA加密與驗(yàn)證,應(yīng)用場(chǎng)景為與其他平臺(tái)對(duì)接接口時(shí),通過(guò)RSA加密和解密驗(yàn)證請(qǐng)求的有效性,在對(duì)接時(shí)雙方互換公鑰,需要的朋友可以參考下2023-12-12SpringMVC實(shí)現(xiàn)數(shù)據(jù)綁定及表單標(biāo)簽
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)數(shù)據(jù)綁定及表單標(biāo)簽的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Java實(shí)現(xiàn)文字滾動(dòng)廣告字幕效果
文字滾動(dòng)廣告字幕是一種常見(jiàn)的動(dòng)態(tài)文本展示效果,通常用于展示新聞、廣告或其他動(dòng)態(tài)信息,在本項(xiàng)目中,我們將使用Java的Swing庫(kù)來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文字滾動(dòng)廣告字幕效果,通過(guò)定時(shí)更新文本的位置來(lái)模擬文字的滾動(dòng),需要的朋友可以參考下2025-02-02idea中導(dǎo)入別人的springboot項(xiàng)目的方法(圖文)
這篇文章主要介紹了idea中導(dǎo)入別人的springboot項(xiàng)目的方法(圖文),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Web容器啟動(dòng)過(guò)程中如何執(zhí)行Java類
這篇文章主要介紹了Web容器啟動(dòng)過(guò)程中如何執(zhí)行Java類,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10java利用JEXL實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式編譯
這篇文章主要介紹了java利用JEXL實(shí)現(xiàn)動(dòng)態(tài)表達(dá)式編譯,系統(tǒng)要獲取多個(gè)數(shù)據(jù)源的數(shù)據(jù),并進(jìn)行處理,最后輸出多個(gè)字段。字段的計(jì)算規(guī)則一般是簡(jiǎn)單的取值最多加一點(diǎn)條件判斷,下面是具體的實(shí)現(xiàn)方法2021-04-04淺談JSON的數(shù)據(jù)交換、緩存問(wèn)題和同步問(wèn)題
這篇文章主要介紹了淺談JSON的數(shù)據(jù)交換、緩存問(wèn)題和同步問(wèn)題,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12