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