国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

spring:我是如何解決循環(huán)依賴的?

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

[[360769]]

1.由同事拋的一個(gè)問題開始

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

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

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.  
  7.     @Async 
  8.     public void test1() { 
  9.     } 

  1. @Service 
  2. public class TestService2 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 
  6.  
  7.     public void test2() { 
  8.     } 

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

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

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

  1. 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 offfor example. 

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

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

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

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.  
  7.     public void test1() { 
  8.     } 

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

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

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

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

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

第一種情況:自己依賴自己的直接依賴


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


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


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

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

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


單例的setter注入

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

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.  
  7.     public void test1() { 
  8.     } 

  1. @Service 
  2. public class TestService2 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 
  6.  
  7.     public void test2() { 
  8.     } 

這是一個(gè)經(jīng)典的循環(huán)依賴,但是它能正常運(yùn)行,得益于spring的內(nèi)部機(jī)制,讓我們根本無法感知它有問題,因?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)依賴的: 


圖1

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

那么問題來了,為什么要用第二級(jí)緩存呢?

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

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.     @Autowired 
  7.     private TestService3 testService3; 
  8.  
  9.     public void test1() { 
  10.     } 

  1. @Service 
  2. public class TestService2 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 
  6.  
  7.     public void test2() { 
  8.     } 

  1. @Service 
  2. public class TestService3 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 
  6.  
  7.     public void test3() { 
  8.     } 

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

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

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


圖2

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

這樣不是有問題?

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


圖3

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

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

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

答案就在AbstractAutowireCapableBeanFactory類doCreateBean方法的這段代碼中:

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

多例的setter注入

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

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
  2. @Service 
  3. public class TestService1 { 
  4.  
  5.     @Autowired 
  6.     private TestService2 testService2; 
  7.  
  8.     public void test1() { 
  9.     } 

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 
  2. @Service 
  3. public class TestService2 { 
  4.  
  5.     @Autowired 
  6.     private TestService1 testService1; 
  7.  
  8.     public void test2() { 
  9.     } 

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

為什么呢?

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

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

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

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

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

  1. @Service 
  2. public class TestService3 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 

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

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

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

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

構(gòu)造器注入

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

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     public TestService1(TestService2 testService2) { 
  5.     } 

  1. @Service 
  2. public class TestService2 { 
  3.  
  4.     public TestService2(TestService1 testService1) { 
  5.     } 

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

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

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


從圖中的流程看出構(gòu)造器注入只是添加了三級(jí)緩存,并沒有使用緩存,所以也無法解決循環(huán)依賴問題。

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

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

我那位同事的問題也是這種情況。

  1. @Service 
  2. public class TestService1 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.  
  7.     @Async 
  8.     public void test1() { 
  9.     } 

  1. @Service 
  2. public class TestService2 { 
  3.  
  4.     @Autowired 
  5.     private TestService1 testService1; 
  6.  
  7.     public void test2() { 
  8.     } 

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

  1. 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 offfor example. 

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

答案就在下面這張圖中:


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


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

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

  1. @Service 
  2. publicclass TestService6 { 
  3.  
  4.     @Autowired 
  5.     private TestService2 testService2; 
  6.  
  7.     @Async 
  8.     public void test1() { 
  9.     } 

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

what? 這又是為什么?

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

為什么TestService2比TestService6先加載就沒問題呢?

答案在下面這張圖中:


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

DependsOn循環(huán)依賴

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

  1. @DependsOn(value = "testService2"
  2. @Service 
  3. public class TestService1 { 
  4.  
  5.     @Autowired 
  6.     private TestService2 testService2; 
  7.  
  8.     public void test1() { 
  9.     } 

  1. @DependsOn(value = "testService1"
  2. @Service 
  3. public class TestService2 { 
  4.  
  5.     @Autowired 
  6.     private TestService1 testService1; 
  7.  
  8.     public void test2() { 
  9.     } 

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

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

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

這又是為什么?

答案在AbstractBeanFactory類的doGetBean方法的這段代碼中:

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

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

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


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

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

  1. 使用@Lazy注解,延遲加載
  2. 使用@DependsOn注解,指定加載先后關(guān)系
  3. 修改文件名稱,改變循環(huán)依賴類的加載順序

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

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

多例循環(huán)依賴

這類循環(huán)依賴問題可以通過把bean改成單例的解決。

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

這類循環(huán)依賴問題可以通過使用@Lazy注解解決。

 

責(zé)任編輯:姜華 來源: 蘇三說技術(shù)
相關(guān)推薦

2019-11-26 14:30:20

Spring循環(huán)依賴Java

2023-11-28 08:00:00

SpringJava

2023-10-07 08:40:57

緩存屬性Spring

2022-08-17 07:52:31

Spring循環(huán)依賴單例池

2023-08-09 10:43:21

源碼循環(huán)依賴getBean

2020-06-22 08:07:48

Spring依賴場(chǎng)景

2020-11-27 06:28:55

Spring循環(huán)依賴

2021-01-29 14:14:47

動(dòng)態(tài)代理緩存

2023-10-11 12:35:29

Maven

2021-09-01 11:45:10

Spring循環(huán)依賴面試

2020-02-10 15:50:18

Spring循環(huán)依賴Java

2010-03-11 14:15:24

Python循環(huán)

2023-12-12 17:44:13

三級(jí)緩存Bean

2021-04-01 08:05:01

React無限循環(huán)useEffect()

2020-03-12 15:00:44

JavaSpring依賴

2024-04-12 07:51:05

SpringBean初始化

2023-05-09 07:51:28

Spring循環(huán)依賴

2021-01-30 19:35:44

HDFS單點(diǎn)Hadoop

2023-05-04 08:06:27

Spring循環(huán)依賴

2020-08-06 00:14:16

Spring IoC依賴注入開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

九色porny自拍| 欧美性xxxx在线播放| 91久久久久久久久久久久久| 欧洲一区在线电影| 国产成人综合视频| 久久中文亚洲字幕| 日韩在线影院| 亚洲成人影院在线观看| 国产福利成人在线| 日韩视频―中文字幕| 亚洲精品中文字幕女同| 国产精品久久久久9999| 日韩精品中文字幕在线| 欧美日韩成人综合| 亚洲精品一区二区三区四区高清| 综合精品久久久| 国产精品亚洲第一区在线暖暖韩国| 日韩黄色在线观看| 欧美亚洲视频| 妞干网2018| 国内自拍欧美激情| 国产日本亚洲高清| aaa国产精品视频| jizz在亚洲| 日韩免费精品视频| 一区二区欧美在线观看| 日韩av自拍| 男人的天堂在线视频| 91九色国产视频| 日本乱码高清不卡字幕| 精品1区2区3区4区| 都市激情久久综合| 日韩在线观看a| 中文字幕9999| 久久亚洲二区三区| 一本久久青青| 免费一级在线观看播放网址| 成人精品一二区| 欧美日韩国产在线播放网站| 国产欧美日韩亚洲一区二区三区| av色综合久久天堂av色综合在| 日本视频精品一区| 国产一区二区三区视频| 久久综合久久鬼色| 视频精品在线观看| 成人性生交大片免费看午夜| 日本一区免费观看| 在线观看国产精品91| 久久久国产一区二区三区四区小说 | 国产女同性恋一区二区| 女同另类激情重口| 午夜国产在线| 日本精品免费| 在线电影欧美日韩一区二区私密| 日本一区二区三区dvd视频在线| 成人情趣视频网站| wwwav在线| 麻豆tv在线播放| 欧美一级免费视频| 欧美精品在线一区二区| 成人深夜在线观看| 国产欧美日韩| 18在线观看的| 国产91对白刺激露脸在线观看| 国产精品成人播放| 欧美大片国产精品| 丰满少妇被猛烈进入高清播放| 一区二区欧美久久| 亚洲午夜国产一区99re久久| 天堂影院一区二区| 亚洲国产中文在线| 国产免费av高清在线| 久草免费福利在线| 91精品国产综合久久香蕉的用户体验 | 国产97在线|日韩| 欧美日韩一区二区三区在线| 国产美女久久久久| 在线成人动漫av| 国产午夜精品久久久久免费视| 欧美日韩精品在线一区二区 | 精品日本一线二线三线不卡| 91色婷婷久久久久合中文| 欧美午夜精品一区二区三区电影| 麻豆tv入口在线看| 国产日韩一区二区在线观看| 北条麻妃高清一区| 久久精品国产一区二区电影| 欧美中文一区二区三区| 高清日韩电视剧大全免费| 四虎8848精品成人免费网站| 欧美国产大片| 日本免费一区二区三区最新| 少妇av一区二区三区无码| 亚洲最大成人网色| 日韩视频精品在线| 欧美色视频在线| 久久久99久久| 丝袜亚洲精品中文字幕一区| 欧美欧美黄在线二区| 午夜欧美巨大性欧美巨大 | 午夜精品久久久久久毛片| 在线看你懂得| 妞干网在线观看视频| 国产厕所精品在线观看| 欧美国产日韩二区| 欧美一级欧美三级在线观看| 一级精品视频在线观看宜春院| 国产精品99久久久久久久vr| 在线国产精品一区| 色综合www| 色天使综合视频| 日本蜜桃在线观看| 亚洲男男gay视频| 久久久免费视频网站| 日本电影一区二区三区| 国产97色在线|日韩| 色伦专区97中文字幕| 日韩亚洲欧美一区| 一本在线高清不卡dvd| 中文字幕一区日韩精品欧美| 成人性色生活片| 亚洲人a成www在线影院| 国产黄a三级三级三级av在线看| 日本激情综合网| 免费在线精品视频| 久久久综合亚洲91久久98| 国产精品露脸av在线| 欧美成人一二三| 亚洲免费av电影| 欧美疯狂性受xxxxx喷水图片| 亚洲一区二区三区四区在线免费观看| 91毛片在线观看| 国产精品99久久久| 日本欧美一区二区| 国模大胆一区二区三区| 精品在线99| 成人爽a毛片| 在线免费成人| 成人免费无遮挡| 欧美xxxx做受欧美88bbw| 经典三级在线| 黄页大全在线免费观看| 性猛交ⅹ×××乱大交| 久操网在线观看| 操bbb操bbb| 亚洲区一区二区三区| 蜜桃成人在线| 国产精品一区二区不卡视频| 成人在线视频福利| 国产精品久久久久久久久久三级 | 亚洲新中文字幕| 精品国产欧美一区二区| 欧美三片在线视频观看| 黄色一区二区在线| 亚洲一区二区在线免费观看视频 | 精品国一区二区三区| 欧美日韩精品系列| 精品视频一区二区不卡| 在线观看视频一区二区| 欧美午夜www高清视频| 亚洲成av人综合在线观看| 亚洲综合色网站| 亚洲综合色成人| 亚洲福利视频导航| 亚洲午夜影视影院在线观看| 亚洲一二三四区| 午夜国产精品一区| 色婷婷综合激情| 欧美亚州韩日在线看免费版国语版| 一本一道久久a久久精品综合蜜臀| 欧美日韩午夜视频在线观看| 狠狠躁夜夜躁人人躁婷婷91| 色爱区综合激月婷婷| 欧美日本韩国一区二区三区视频 | 国偷自产一区二区免费视频| 欧美hdxxx| 日本蜜桃在线观看视频| 免费电影日韩网站| 丁香婷婷久久| 成人看片毛片免费播放器| 国产高清精品二区| 久久精品福利| av亚洲在线观看| 日韩电影免费网址| 欧美日本一区| 日av在线不卡| 成人18视频日本| 中文字幕亚洲综合久久菠萝蜜| 亚洲成人免费电影| 在线成人午夜影院| 亚洲一区二区黄| 性欧美长视频免费观看不卡 | 国产精品久久影院| 亚洲在线一区二区三区| 在线精品视频免费观看| 欧美成人vps| 日韩小视频网址| 国产精品 欧美在线| 国产伦精品一区二区三区视频孕妇| 亚洲欧美日韩国产成人综合一二三区|