<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 7899字,需瀏覽 16分鐘

           ·

          2021-05-29 13:15


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

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

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

          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

          @Async
          public void test1() {
          }
          }
          @Service
          public class TestService2 {

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }

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

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

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

          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)依賴。

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

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

          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

          public void test1() {
          }
          }

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

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

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

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

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

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

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

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

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

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

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

          單例的setter注入

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

          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

          public void test1() {
          }
          }
          @Service
          public class TestService2 {

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }

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

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

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

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

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

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

                                     圖1


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

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

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

          @Service
          public class TestService1 {

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

          public void test1() {
          }
          }
          @Service
          public class TestService2 {

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }
          @Service
          public class TestService3 {

          @Autowired
          private TestService1 testService1;

          public void test3() {
          }
          }

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

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

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

                                   圖2


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

          這樣不是有問題?

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

                                   圖3


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

          答:不行,因?yàn)榧偃缒阆雽μ砑拥饺壘彺嬷械膶?shí)例對象進(jìn)行增強(qiáng),直接用實(shí)例對象是行不通的。

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

          答案就在AbstractAutowireCapableBeanFactorydoCreateBean方法的這段代碼中:

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

          多例的setter注入

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

          @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

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

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }

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

          為什么呢?

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

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

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

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

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

          @Service
          public class TestService3 {

          @Autowired
          private TestService1 testService1;
          }

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

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

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

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

          構(gòu)造器注入

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

          @Service
          public class TestService1 {

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

          public TestService2(TestService1 testService1) {
          }
          }

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

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

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

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

          單例的代理對象setter注入

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

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

          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

          @Async
          public void test1() {
          }
          }
          @Service
          public class TestService2 {

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }

          從前面得知程序啟動會報(bào)錯(cuò),出現(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)依賴呢?

          答案就在下面這張圖中:

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

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

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

          @Service
          publicclass TestService6 {

          @Autowired
          private TestService2 testService2;

          @Async
          public void test1() {
          }
          }

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

          what? 這又是為什么?

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

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

          答案在下面這張圖中:

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



          DependsOn循環(huán)依賴

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

          @DependsOn(value = "testService2")
          @Service
          public class TestService1 {

          @Autowired
          private TestService2 testService2;

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

          @Autowired
          private TestService1 testService1;

          public void test2() {
          }
          }

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

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

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

          這又是為什么?

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

          它會檢查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)依賴。目前包含下面幾種情況:

          生成代理對象產(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注解解決。


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

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

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

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

                

          個(gè)人微信

                                

             聽說點(diǎn)個(gè)“在看”就有好運(yùn)氣~


          推薦閱讀:

          explain | 索引優(yōu)化的這把絕世好劍,你真的會用嗎?

          求你別再用swagger了,給你推薦幾個(gè)在線文檔生成神器

          讓人頭痛的大事務(wù)問題到底要如何解決?

          mybatis日志功能是如何設(shè)計(jì)的?

          zuul如果兩個(gè)filter的order一樣,是如何排序的?

          mysql的這幾個(gè)坑你踩過沒?真是防不勝防
          線程池最佳線程數(shù)量到底要如何配置?
          這8種保證線程安全的技術(shù)你都知道嗎?



          瀏覽 63
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美动态视频 | 乳交打奶炮泄精合集 | 在线无码视频免费观看 | 国产伦精品一区二区三区视频女 | 日韩人妻无码一区二区三区99 |