最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Spring是如何解決循環(huán)依賴(lài)問(wèn)題?

2023-07-20 22:17 作者:蘇三說(shuō)技術(shù)  | 我要投稿


1.由同事拋的一個(gè)問(wèn)題開(kāi)始

最近項(xiàng)目組的一個(gè)同事遇到了一個(gè)問(wèn)題,問(wèn)我的意見(jiàn),一下子引起的我的興趣,因?yàn)檫@個(gè)問(wèn)題我也是第一次遇到。平時(shí)自認(rèn)為對(duì)spring循環(huán)依賴(lài)問(wèn)題還是比較了解的,直到遇到這個(gè)和后面的幾個(gè)問(wèn)題后,重新刷新了我的認(rèn)識(shí)。

我們先看看當(dāng)時(shí)出問(wèn)題的代碼片段:

@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?@Async
? ?public void test1() {
? ?}
}
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}

這兩段代碼中定義了兩個(gè)Service類(lèi):TestService1TestService2,在TestService1中注入了TestService2的實(shí)例,同時(shí)在TestService2中注入了TestService1的實(shí)例,這里構(gòu)成了循環(huán)依賴(lài)

只不過(guò),這不是普通的循環(huán)依賴(lài),因?yàn)門(mén)estService1的test1方法上加了一個(gè)@Async注解。

大家猜猜程序啟動(dòng)后運(yùn)行結(jié)果會(huì)怎樣?

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

報(bào)錯(cuò)了。。。原因是出現(xiàn)了循環(huán)依賴(lài)。

「不科學(xué)呀,spring不是號(hào)稱(chēng)能解決循環(huán)依賴(lài)問(wèn)題嗎,怎么還會(huì)出現(xiàn)?」

如果把上面的代碼稍微調(diào)整一下:

@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?public void test1() {
? ?}
}

把TestService1的test1方法上的@Async注解去掉,TestService1TestService2都需要注入對(duì)方的實(shí)例,同樣構(gòu)成了循環(huán)依賴(lài)。

但是重新啟動(dòng)項(xiàng)目,發(fā)現(xiàn)它能夠正常運(yùn)行。這又是為什么?

帶著這兩個(gè)問(wèn)題,讓我們一起開(kāi)始spring循環(huán)依賴(lài)的探秘之旅。

2.什么是循環(huán)依賴(lài)?

循環(huán)依賴(lài):說(shuō)白是一個(gè)或多個(gè)對(duì)象實(shí)例之間存在直接或間接的依賴(lài)關(guān)系,這種依賴(lài)關(guān)系構(gòu)成了構(gòu)成一個(gè)環(huán)形調(diào)用。

第一種情況:自己依賴(lài)自己的直接依賴(lài)

第二種情況:兩個(gè)對(duì)象之間的直接依賴(lài)

第三種情況:多個(gè)對(duì)象之間的間接依賴(lài)

前面兩種情況的直接循環(huán)依賴(lài)比較直觀,非常好識(shí)別,但是第三種間接循環(huán)依賴(lài)的情況有時(shí)候因?yàn)闃I(yè)務(wù)代碼調(diào)用層級(jí)很深,不容易識(shí)別出來(lái)。

3.循環(huán)依賴(lài)的N種場(chǎng)景

spring中出現(xiàn)循環(huán)依賴(lài)主要有以下場(chǎng)景:

單例的setter注入

這種注入方式應(yīng)該是spring用的最多的,代碼如下:

@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?public void test1() {
? ?}
}
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}

這是一個(gè)經(jīng)典的循環(huán)依賴(lài),但是它能正常運(yùn)行,得益于spring的內(nèi)部機(jī)制,讓我們根本無(wú)法感知它有問(wèn)題,因?yàn)閟pring默默幫我們解決了。

spring內(nèi)部有三級(jí)緩存:

  • singletonObjects 一級(jí)緩存,用于保存實(shí)例化、注入、初始化完成的bean實(shí)例

  • earlySingletonObjects 二級(jí)緩存,用于保存實(shí)例化完成的bean實(shí)例

  • singletonFactories 三級(jí)緩存,用于保存bean創(chuàng)建工廠,以便于后面擴(kuò)展有機(jī)會(huì)創(chuàng)建代理對(duì)象。

下面用一張圖告訴你,spring是如何解決循環(huán)依賴(lài)的:

? ? ? ? ? ? ? ? ? ? ? ? ? ?圖1


細(xì)心的朋友可能會(huì)發(fā)現(xiàn)在這種場(chǎng)景中第二級(jí)緩存作用不大。

那么問(wèn)題來(lái)了,為什么要用第二級(jí)緩存呢?

試想一下,如果出現(xiàn)以下這種情況,我們要如何處理?

@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;
? ?@Autowired
? ?private TestService3 testService3;

? ?public void test1() {
? ?}
}
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}
@Service
publicclass TestService3 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test3() {
? ?}
}

TestService1依賴(lài)于TestService2和TestService3,而TestService2依賴(lài)于TestService1,同時(shí)TestService3也依賴(lài)于TestService1。

按照上圖的流程可以把TestService1注入到TestService2,并且TestService1的實(shí)例是從第三級(jí)緩存中獲取的。

假設(shè)不用第二級(jí)緩存,TestService1注入到TestService3的流程如圖:

? ? ? ? ? ? ? ? ? ? ? ? ?圖2


TestService1注入到TestService3又需要從第三級(jí)緩存中獲取實(shí)例,而第三級(jí)緩存里保存的并非真正的實(shí)例對(duì)象,而是ObjectFactory對(duì)象。說(shuō)白了,兩次從三級(jí)緩存中獲取都是ObjectFactory對(duì)象,而通過(guò)它創(chuàng)建的實(shí)例對(duì)象每次可能都不一樣的。

這樣不是有問(wèn)題?

為了解決這個(gè)問(wèn)題,spring引入的第二級(jí)緩存。上面圖1其實(shí)TestService1對(duì)象的實(shí)例已經(jīng)被添加到第二級(jí)緩存中了,而在TestService1注入到TestService3時(shí),只用從第二級(jí)緩存中獲取該對(duì)象即可。

? ? ? ? ? ? ? ? ? ? ? ? ?圖3


還有個(gè)問(wèn)題,第三級(jí)緩存中為什么要添加ObjectFactory對(duì)象,直接保存實(shí)例對(duì)象不行嗎?

答:不行,因?yàn)榧偃缒阆雽?duì)添加到三級(jí)緩存中的實(shí)例對(duì)象進(jìn)行增強(qiáng),直接用實(shí)例對(duì)象是行不通的。

針對(duì)這種場(chǎng)景spring是怎么做的呢?

答案就在AbstractAutowireCapableBeanFactory類(lèi)doCreateBean方法的這段代碼中:

它定義了一個(gè)匿名內(nèi)部類(lèi),通過(guò)getEarlyBeanReference方法獲取代理對(duì)象,其實(shí)底層是通過(guò)AbstractAutoProxyCreator類(lèi)的getEarlyBeanReference生成代理對(duì)象。

多例的setter注入

這種注入方法偶然會(huì)有,特別是在多線程的場(chǎng)景下,具體代碼如下:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?public void test1() {
? ?}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}

很多人說(shuō)這種情況spring容器啟動(dòng)會(huì)報(bào)錯(cuò),其實(shí)是不對(duì)的,我非常負(fù)責(zé)任的告訴你程序能夠正常啟動(dòng)。

為什么呢?

其實(shí)在AbstractApplicationContext類(lèi)的refresh方法中告訴了我們答案,它會(huì)調(diào)用finishBeanFactoryInitialization方法,該方法的作用是為了spring容器啟動(dòng)的時(shí)候提前初始化一些bean。該方法的內(nèi)部又調(diào)用了preInstantiateSingletons方法

標(biāo)紅的地方明顯能夠看出:非抽象、單例 并且非懶加載的類(lèi)才能被提前初始bean。

而多例即SCOPE_PROTOTYPE類(lèi)型的類(lèi),非單例,不會(huì)被提前初始化bean,所以程序能夠正常啟動(dòng)。

如何讓他提前初始化bean呢?

只需要再定義一個(gè)單例的類(lèi),在它里面注入TestService1

@Service
publicclass TestService3 {

? ?@Autowired
? ?private TestService1 testService1;
}

重新啟動(dòng)程序,執(zhí)行結(jié)果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

果然出現(xiàn)了循環(huán)依賴(lài)。

注意:這種循環(huán)依賴(lài)問(wèn)題是無(wú)法解決的,因?yàn)樗鼪](méi)有用緩存,每次都會(huì)生成一個(gè)新對(duì)象。

構(gòu)造器注入

這種注入方式現(xiàn)在其實(shí)用的已經(jīng)非常少了,但是我們還是有必要了解一下,看看如下代碼:

@Service
publicclass TestService1 {

? ?public TestService1(TestService2 testService2) {
? ?}
}
@Service
publicclass TestService2 {

? ?public TestService2(TestService1 testService1) {
? ?}
}

運(yùn)行結(jié)果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出現(xiàn)了循環(huán)依賴(lài),為什么呢?

從圖中的流程看出構(gòu)造器注入沒(méi)能添加到三級(jí)緩存,也沒(méi)有使用緩存,所以也無(wú)法解決循環(huán)依賴(lài)問(wèn)題。

單例的代理對(duì)象setter注入

這種注入方式其實(shí)也比較常用,比如平時(shí)使用:@Async注解的場(chǎng)景,會(huì)通過(guò)AOP自動(dòng)生成代理對(duì)象。

我那位同事的問(wèn)題也是這種情況。

@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?@Async
? ?public void test1() {
? ?}
}
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}

從前面得知程序啟動(dòng)會(huì)報(bào)錯(cuò),出現(xiàn)了循環(huán)依賴(lài):

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

為什么會(huì)循環(huán)依賴(lài)呢?

答案就在下面這張圖中:

說(shuō)白了,bean初始化完成之后,后面還有一步去檢查:第二級(jí)緩存 和 原始對(duì)象 是否相等。由于它對(duì)前面流程來(lái)說(shuō)無(wú)關(guān)緊要,所以前面的流程圖中省略了,但是在這里是關(guān)鍵點(diǎn),我們重點(diǎn)說(shuō)說(shuō):

那位同事的問(wèn)題正好是走到這段代碼,發(fā)現(xiàn)第二級(jí)緩存 和 原始對(duì)象不相等,所以拋出了循環(huán)依賴(lài)的異常。

如果這時(shí)候把TestService1改個(gè)名字,改成:TestService6,其他的都不變。

@Service
publicclass TestService6 {

? ?@Autowired
? ?private TestService2 testService2;

? ?@Async
? ?public void test1() {
? ?}
}

再重新啟動(dòng)一下程序,神奇般的好了。

what??這又是為什么?

這就要從spring的bean加載順序說(shuō)起了,默認(rèn)情況下,spring是按照文件完整路徑遞歸查找的,按路徑+文件名排序,排在前面的先加載。所以TestService1比TestService2先加載,而改了文件名稱(chēng)之后,TestService2比TestService6先加載。

為什么TestService2比TestService6先加載就沒(méi)問(wèn)題呢?

答案在下面這張圖中:

這種情況testService6中其實(shí)第二級(jí)緩存是空的,不需要跟原始對(duì)象判斷,所以不會(huì)拋出循環(huán)依賴(lài)。



DependsOn循環(huán)依賴(lài)

還有一種有些特殊的場(chǎng)景,比如我們需要在實(shí)例化Bean A之前,先實(shí)例化Bean B,這個(gè)時(shí)候就可以使用@DependsOn注解。

@DependsOn(value = "testService2")
@Service
publicclass TestService1 {

? ?@Autowired
? ?private TestService2 testService2;

? ?public void test1() {
? ?}
}
@DependsOn(value = "testService1")
@Service
publicclass TestService2 {

? ?@Autowired
? ?private TestService1 testService1;

? ?public void test2() {
? ?}
}

程序啟動(dòng)之后,執(zhí)行結(jié)果:

Circular depends-on relationship between 'testService2' and 'testService1'

這個(gè)例子中本來(lái)如果TestService1和TestService2都沒(méi)有加@DependsOn注解是沒(méi)問(wèn)題的,反而加了這個(gè)注解會(huì)出現(xiàn)循環(huán)依賴(lài)問(wèn)題。

這又是為什么?

答案在AbstractBeanFactory類(lèi)的doGetBean方法的這段代碼中:

它會(huì)檢查dependsOn的實(shí)例有沒(méi)有循環(huán)依賴(lài),如果有循環(huán)依賴(lài)則拋異常。

4.出現(xiàn)循環(huán)依賴(lài)如何解決?

項(xiàng)目中如果出現(xiàn)循環(huán)依賴(lài)問(wèn)題,說(shuō)明是spring默認(rèn)無(wú)法解決的循環(huán)依賴(lài),要看項(xiàng)目的打印日志,屬于哪種循環(huán)依賴(lài)。目前包含下面幾種情況:

生成代理對(duì)象產(chǎn)生的循環(huán)依賴(lài)

這類(lèi)循環(huán)依賴(lài)問(wèn)題解決方法很多,主要有:

  1. 使用@Lazy注解,延遲加載

  2. 使用@DependsOn注解,指定加載先后關(guān)系

  3. 修改文件名稱(chēng),改變循環(huán)依賴(lài)類(lèi)的加載順序

使用@DependsOn產(chǎn)生的循環(huán)依賴(lài)

這類(lèi)循環(huán)依賴(lài)問(wèn)題要找到@DependsOn注解循環(huán)依賴(lài)的地方,迫使它不循環(huán)依賴(lài)就可以解決問(wèn)題。

多例循環(huán)依賴(lài)

這類(lèi)循環(huán)依賴(lài)問(wèn)題可以通過(guò)把bean改成單例的解決。

構(gòu)造器循環(huán)依賴(lài)

這類(lèi)循環(huán)依賴(lài)問(wèn)題可以通過(guò)使用@Lazy注解解決。


最后說(shuō)一句(求關(guān)注,別白嫖我)

如果這篇文章對(duì)您有所幫助,或者有所啟發(fā)的話,幫忙掃描下發(fā)二維碼關(guān)注一下,您的支持是我堅(jiān)持寫(xiě)作最大的動(dòng)力。

求一鍵三連:點(diǎn)贊、轉(zhuǎn)發(fā)、在看。

在公眾號(hào)中回復(fù):面試、代碼神器、開(kāi)發(fā)手冊(cè)、時(shí)間管理有超贊的粉絲福利,另外回復(fù):加群,可以跟很多BAT大廠的前輩交流和學(xué)習(xí)。


最后歡迎大家加入蘇三的知識(shí)星球【Java突擊隊(duì)】,一起學(xué)習(xí)。

星球中有很多獨(dú)家的干貨內(nèi)容,比如:Java后端學(xué)習(xí)路線,分享實(shí)戰(zhàn)項(xiàng)目,源碼分析,百萬(wàn)級(jí)系統(tǒng)設(shè)計(jì),系統(tǒng)上線的一些坑,MQ專(zhuān)題,真實(shí)面試題,每天都會(huì)回答大家提出的問(wèn)題,免費(fèi)修改簡(jiǎn)歷,免費(fèi)回答工作中的問(wèn)題。

星球目前開(kāi)通了6個(gè)優(yōu)質(zhì)專(zhuān)欄:技術(shù)選型、系統(tǒng)設(shè)計(jì)、Spring源碼解讀、痛點(diǎn)問(wèn)題、高頻面試題 和 性能優(yōu)化。



  • 每一個(gè)專(zhuān)欄都是大家非常關(guān)心,和非常有價(jià)值的話題,我相信在專(zhuān)欄中你會(huì)學(xué)到很多東西,值回票價(jià)。

  • 目前僅需99元,后面應(yīng)該會(huì)漲到199元。


    加入星球如果不滿(mǎn)意,3天內(nèi)包退。

Spring是如何解決循環(huán)依賴(lài)問(wèn)題?的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
大新县| 巨鹿县| 南郑县| 夏河县| 康定县| 涟水县| 彭泽县| 安丘市| 莲花县| 蓬莱市| 崇仁县| 永春县| 延吉市| 平度市| 汝阳县| 闵行区| 玉田县| 镇安县| 得荣县| 普陀区| 红河县| 南丹县| 寿光市| 易门县| 昌邑市| 施甸县| 吴桥县| 思南县| 武义县| 徐水县| 南部县| 江川县| 台东县| 疏附县| 女性| 南丹县| 志丹县| 怀远县| 昂仁县| 奉贤区| 梅州市|