spring中如何解決循環(huán)依賴

這期內(nèi)容當中小編將會給大家?guī)碛嘘Pspring中如何解決循環(huán)依賴,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

創(chuàng)新互聯(lián)公司專注于旅順口企業(yè)網(wǎng)站建設,響應式網(wǎng)站建設,商城開發(fā)。旅順口網(wǎng)站建設公司,為旅順口等地區(qū)提供建站服務。全流程按需設計網(wǎng)站,專業(yè)設計,全程項目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務

1.由同事拋的一個問題開始

我們先看看當時出問題的代碼片段:

  1. @Service 

  2. public class TestService1 { 

  3.  

  4.     @Autowired 

  5.     private TestService2 testService2; 

  6.  

  7.     @Async 

  8.     public void test1() { 

  9.     } 


@Service public class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

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

只不過,這不是普通的循環(huán)依賴,因為TestService1的test1方法上加了一個@Async注解。

大家猜猜程序啟動后運行結果會怎樣?

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.

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

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

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

@Service public class TestService1 {      @Autowired     private TestService2 testService2;      public void test1() {     } }

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

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

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

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

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

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

spring中如何解決循環(huán)依賴

第二種情況:兩個對象之間的直接依賴

spring中如何解決循環(huán)依賴

第三種情況:多個對象之間的間接依賴

spring中如何解決循環(huán)依賴

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

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

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

spring中如何解決循環(huán)依賴

單例的setter注入

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

  1. @Service 

  2. public class TestService1 { 

  3.  

  4.     @Autowired 

  5.     private TestService2 testService2; 

  6.  

  7.     public void test1() { 

  8.     } 


@Service public class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

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

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

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

  • earlySingletonObjects 二級緩存,用于保存實例化完成的bean實例

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

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

spring中如何解決循環(huán)依賴

圖1

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

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

試想一下,如果出現(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.     } 


@Service public class TestService3 {      @Autowired     private TestService1 testService1;      public void test3() {     } }

TestService1依賴于TestService2和TestService3,而TestService2依賴于TestService1,同時TestService3也依賴于TestService1。

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

假設不用第二級緩存,TestService1注入到TestService3的流程如圖:

spring中如何解決循環(huán)依賴

圖2

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

這樣不是有問題?

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

spring中如何解決循環(huán)依賴

圖3

還有個問題,第三級緩存中為什么要添加ObjectFactory對象,直接保存實例對象不行嗎?

答:不行,因為假如你想對添加到三級緩存中的實例對象進行增強,直接用實例對象是行不通的。

針對這種場景spring是怎么做的呢?

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

spring中如何解決循環(huán)依賴

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

多例的setter注入

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

  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 

  2. @Service 

  3. public class TestService1 { 

  4.  

  5.     @Autowired 

  6.     private TestService2 testService2; 

  7.  

  8.     public void test1() { 

  9.     } 


@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Service public class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

很多人說這種情況spring容器啟動會報錯,其實是不對的,我非常負責任的告訴你程序能夠正常啟動。

為什么呢?

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

spring中如何解決循環(huán)依賴

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

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

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

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

@Service public class TestService3 {      @Autowired     private TestService1 testService1; }

重新啟動程序,執(zhí)行結果:

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

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

注意:這種循環(huán)依賴問題是無法解決的,因為它沒有用緩存,每次都會生成一個新對象。

構造器注入

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

  1. @Service 

  2. public class TestService1 { 

  3.  

  4.     public TestService1(TestService2 testService2) { 

  5.     } 


@Service public class TestService2 {      public TestService2(TestService1 testService1) {     } }

運行結果:

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

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

spring中如何解決循環(huán)依賴

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

單例的代理對象setter注入

這種注入方式其實也比較常用,比如平時使用:@Async注解的場景,會通過AOP自動生成代理對象。

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

  1. @Service 

  2. public class TestService1 { 

  3.  

  4.     @Autowired 

  5.     private TestService2 testService2; 

  6.  

  7.     @Async 

  8.     public void test1() { 

  9.     } 


@Service public class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

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

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án)依賴呢?

答案就在下面這張圖中:

spring中如何解決循環(huán)依賴

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

spring中如何解決循環(huán)依賴

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

如果這時候把TestService1改個名字,改成:TestService6,其他的都不變。

@Service publicclass TestService6 {      @Autowired     private TestService2 testService2;      @Async     public void test1() {     } }

再重新啟動一下程序,神奇般的好了。

what? 這又是為什么?

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

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

答案在下面這張圖中:

spring中如何解決循環(huán)依賴

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

DependsOn循環(huán)依賴

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

  1. @DependsOn(value = "testService2") 

  2. @Service 

  3. public class TestService1 { 

  4.  

  5.     @Autowired 

  6.     private TestService2 testService2; 

  7.  

  8.     public void test1() { 

  9.     } 


@DependsOn(value = "testService1") @Service public class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

程序啟動之后,執(zhí)行結果:

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

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

這又是為什么?

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

spring中如何解決循環(huán)依賴

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

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

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

spring中如何解決循環(huán)依賴

上述就是小編為大家分享的spring中如何解決循環(huán)依賴了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

分享題目:spring中如何解決循環(huán)依賴
文章地址:http://www.muchs.cn/article48/pieehp.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護、微信公眾號ChatGPT、標簽優(yōu)化定制開發(fā)、網(wǎng)站建設

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設公司