<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)依賴的,它支持哪種循環(huán)依賴?

          共 11789字,需瀏覽 24分鐘

           ·

          2021-09-03 01:26

          公眾號關(guān)注 “GitHub今日熱榜
          設(shè)為 “星標(biāo)”,帶你挖掘更多開發(fā)神器!







          結(jié)論


          先說結(jié)論:


          • 單例bean可以通過@Autowrited進(jìn)行循環(huán)依賴

          • 單例bean不可以通過構(gòu)造函數(shù)進(jìn)行循環(huán)依賴

          • 多例bean中不可以存在循環(huán)依賴


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


          當(dāng)對象A中持有對對象B的引用,同時對象B中也持有對對象A的引用,就發(fā)生了循環(huán)依賴


          spring是如何解決循環(huán)依賴問題的


          通過前面兩篇文章bean的實(shí)例化和注解收集和依賴注入的實(shí)現(xiàn)方式、如何阻止依賴注入和bean的初始化工作我們了解到spring將bean的創(chuàng)建分為實(shí)例化、依賴注入、初始化三個部分。只要實(shí)例化完成,那么這個bean對象就算是在堆中被創(chuàng)建出來了,這時相當(dāng)于只是new了一個對象,但是里面的很多屬性可能還沒值(因?yàn)檫€沒做DI)。spring之所以也可以在一定程度上解決循環(huán)依賴,也是得益于上面這個思想。


          循環(huán)依賴代碼


          @Component
          public class CycleRefA {

              /**
               * A中有B
               */

              @Autowired
              private CycleRefB cycleRefB;

              public void hello(){
                  System.out.println("A:hello:"+cycleRefB);
              }
          }


          @Component
          public class CycleRefB {

              /**
               * B中有A
               */

              @Autowired
              private CycleRefA cycleRefA;

              public void hello(){
                  System.out.println("B:hello:"+cycleRefA);
              }
          }


          @Test
          public void test1(){
              ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
              CycleRefA cycleRefA = applicationContext.getBean(CycleRefA.class);
              cycleRefA.hello();
              CycleRefB cycleRefB = applicationContext.getBean(CycleRefB.class);
              cycleRefB.hello();
          }


          我們看下運(yùn)行結(jié)果:



          從結(jié)果中可以看到,CycleRefA和CycleRefB都成功的被加入到spring容器中了,并且,實(shí)現(xiàn)了A中有B,B中有A的效果


          原理探究


          我認(rèn)為循環(huán)依賴只是我們給A引用B,B引用A這種現(xiàn)象的一種描述,實(shí)際上spring只是通過bean的實(shí)例化流程就解決了這個問題,并沒有拉出一大段邏輯來特意處理循環(huán)依賴。所以,我們以上面的CycleRefA和CycleRefB為例,看看spring是如何初始化它們的。spring的getBean的邏輯大體如下:




          借著上面這個圖,我們說下CycleRefA是如何初始化的:


          1、首先,當(dāng)調(diào)用到getBean時,會通過調(diào)用getSingleton(beanName)嘗試從緩存中獲取CycleRefA的實(shí)例

              


          2、因?yàn)镃ycleRefA還沒有被創(chuàng)建,這時緩存里一定是沒有的


          3、然后就會調(diào)用到getSingleton(beanName,singletonFactory)來創(chuàng)建CycleRefA實(shí)例


              

          4、開始創(chuàng)建CycleRefA的實(shí)例前,會將當(dāng)前bean的beanName存到singletonsCurrentlyInCreation,主要是為了標(biāo)記一下當(dāng)前正在創(chuàng)建的bean



          5、這時就會調(diào)用singletonFactory.getObject(),這里的singletonFactory其實(shí)是ObjectFactory接口,真正執(zhí)行的是外面?zhèn)魅氲腸reateBean方法


          6、通過無參的構(gòu)造器實(shí)例化CycleRefA


          7、將CycleRefA加入到三級緩存中


          8、populateBean,對CycleRefA進(jìn)行依賴注入

              

          9、由AutowiredAnnotationBeanPostProcessor來處理@Autowrited注解,在依賴注入的過程中發(fā)現(xiàn)CycleRefA引用CycleRefB,所以觸發(fā)了CycleRefB的getBean操作


          10、跳過一大段前面重復(fù)的步驟…


          11、這時,又來到了CycleRefB的populateBean


          12、由于之前已經(jīng)將CycleRefA的objectFactory放入到了三級緩存中,所以CycleRefB成功的獲取到了CycleRefA


          13、CycleRefB初始化完畢,將CycleRefB放入單例池中


          14、CycleRefA依賴注入完畢


          15、bean創(chuàng)建完畢后,調(diào)用afterSingletonCreation(beanName) 將當(dāng)前bean從singletonsCurrentlyInCreation中移除


          16、如果bean創(chuàng)建成功,那么調(diào)用addSingleton(beanName, singletonObject)將當(dāng)前bean注冊到單例池中,移除二級、三級緩存,同時注冊到registeredSingletons中


          17、CycleRefA被創(chuàng)建成功


          總結(jié)


          簡單來說,spring處理循環(huán)依賴時用到了三個級別的緩存,所有的單例對象都要先從一級緩存也就是singletonObjects中取,如果一級緩存中沒有,那就從二級緩(earlySingletonObjects)存中取,如果二級緩存中沒有,那就從三級緩存(singletonFactories)中取,但是三級緩存緩存的是一個objectFactory,需要調(diào)用getObject()才能獲取到對象,獲取到對象后,將對象緩存到二級緩存中,同時移除這個bean的三級緩存。


          為什么要有二級緩存?


          在默認(rèn)情況下,二級緩存是空的。spring本身不確定是不是真的會發(fā)生循環(huán)依賴,當(dāng)一個對象進(jìn)入到二級緩存后,這個對象一定已經(jīng)發(fā)生循環(huán)依賴了。


          三級緩存之所以是個objectFactory,是因?yàn)閷ο笠崆氨┞叮侨绻写恚枰崆皩⒋韺ο蟊┞冻鰜怼M瑫r,這個暴露過程只能執(zhí)行一次,所以會把objectFactory創(chuàng)建出來的對象緩存到二級緩存中。所以,按照我的理解,二級緩存更像是三級緩存的緩存。


          在什么情況下會需要用到二級緩存


          假如對象A引用了對象B,對象B引用了對象C和對象A,同時對象C又引用了對象A。


          1. 當(dāng)A初始化時,會觸發(fā)B的getBean操作,然后觸發(fā)C的getBean。

          2. 這時會調(diào)用A的objectFactory.getObject(),創(chuàng)建對象A的earlySingletonObject。

          3. 將對象A的earlySingletonObject添加到二級緩存的同時將A從三級緩存中移除。

          4. 這時C創(chuàng)建完畢。

          5. 對象B繼續(xù)DI,然后將二級緩存中已經(jīng)存在的A依賴注入進(jìn)來,對象B完成創(chuàng)建。

          6. 最后對象A創(chuàng)建完畢。

          7. 將二級緩存中A的earlySingletonObject移除。


          反思與思考


          spring解決循環(huán)依賴的思想


          spring這套循環(huán)依賴的解決辦法挺有意思的。大體的思想就是,只要你(bean)能被我創(chuàng)建出來,我就能解決你的循環(huán)依賴問題。因?yàn)樵趕pring看來beanCreation分為創(chuàng)建內(nèi)存對象(俗稱new出來)、依賴注入、初始化三部分。但凡對象被new完畢,就相當(dāng)于在內(nèi)存區(qū)域已經(jīng)占了一個坑了,就算里面還有很多屬性沒有被初始化,也不要緊了。


          會不會存在earlySingletonObject與最后的singletonObject不一致的情況?


          細(xì)思極恐的一件事:在存在循環(huán)依賴的情況下,如果A的earlySingletonObject與A最后的對象不是同一個對象,那A不就相當(dāng)于多例了嗎???比如在使用了AOP的情況下,會有問題嗎?代碼如下:


          @Aspect
          public class MyAspect1 {

              /**
               * 增強(qiáng)CycleRefC.hello,主要是為了讓aop幫忙生成CycleRefC的代理
               */

              @Before("execution(* com.example.spring.beans.CycleRefC.hello(..))")
              public void beforeCHello() {
                  System.out.println("CycleRefC要開始sayHello了");
              }

              /**
               * 增強(qiáng)CycleRefD.hello,主要是為了讓aop幫忙生成CycleRefD的代理
               */

              @Before("execution(* com.example.spring.beans.CycleRefD.hello(..))")
              public void beforeDHello() {
                  System.out.println("CycleRefD要開始sayHello了");
              }
          }


          @Component
          public class CycleRefC {

              @Autowired
              private CycleRefD cycleRefD;

              public void hello() {
                  System.out.println("C:hello:" + cycleRefD);
              }
          }


          @Component
          public class CycleRefD {

              @Autowired
              private CycleRefC cycleRefC;

              public void hello() {
                  System.out.println("D:hello:" + cycleRefC);
              }
          }


          @Test
          public void test2() {
              ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
              CycleRefC cycleRefA = applicationContext.getBean(CycleRefC.class);
              cycleRefA.hello();
              CycleRefD cycleRefD = applicationContext.getBean(CycleRefD.class);
              cycleRefD.hello();
          }


          先看下結(jié)果:



          嗯,結(jié)果沒問題,白擔(dān)心了,還以為發(fā)現(xiàn)了spring的驚天大bug…


          不過,為什么呢???讓我們接著debug探究一下why?


          幾個關(guān)鍵點(diǎn)的debug截圖如下:



          可以看到cycleRefC剛剛開始初始化,緩存里沒有對象,果然,不出意料的走到了doCreateBean這里



          從cycleRefC的populateBean,觸發(fā)了cycleRefD的getBean



          同樣的,cycleRefD也是需要被創(chuàng)建的



          因?yàn)閏ycleRefD中也持有了cycleRefC的引用,所以自然又一次發(fā)了cycleRefC的getBean,但是,這次不同了,三級緩存中已經(jīng)有cycleRefC了。



          不出意外的調(diào)用到了cycleRefC的getEarlyBeanReference



          AbstractAutoProxyCreator作為aop的入口,做了兩件事:1.緩存了被代理前的對象;2:創(chuàng)建代理對象并返回



          所以cycleRefC的getEarlyBeanReference返回的是一個由CGLIB代理的對象



          接下來cycleRefD完成了創(chuàng)建,視角會到cycleRefC的創(chuàng)建這里,此時,cycleRefC已經(jīng)完成了initializeBean



          但是,從截圖上可以看到,exposedObject依然是cycleRefC,此時exposedObject已經(jīng)和earlySingletonObject不一樣了!!!spring用下面的方式解決了這個問題



          當(dāng)exposedObject == bean時,將earlySingletonReference賦值給exposedObject,這樣就保證了earlySingletonReference與exposedObject是同一個對象。還有很重要的一點(diǎn),之所以cycleRefC走完了initializeBean之后對象沒有發(fā)生變化,原因在這里:



          由于循環(huán)依賴的原因,cycleRefC被提前暴露了,所以當(dāng)時earlyProxyReferences中記錄了暴露前的對象,如果在中間的流程中,沒有其他操作對bean進(jìn)行內(nèi)存地址的修改,那么這里就不會再次創(chuàng)建代理。然后,后面的事情就順利成章了。


          怎么才能讓earlySingletonObject與最后的singletonObject真的不一致?


          如果真的不一致,那spring真的會報(bào)錯給你看…


          我們先看下代碼:


          @Component("cycleRefE")
          public class CycleRefE {

              @Autowired
              private CycleRefF cycleRefF;

              public void hello() {
                  System.out.println("E:hello:" + cycleRefF);
              }
          }
          @Component("cycleRefF")
          public class CycleRefF {

              @Autowired
              private CycleRefE cycleRefE;

              public void hello() {
                  System.out.println("F:hello:" + cycleRefE);
              }
          }


          強(qiáng)制代理cycleRefF和cycleRefE



          就是在這里拋出了異常



          異常信息如下:


          org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleRefE': Bean with name 'cycleRefE' has been injected into other beans [cycleRefF] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:631)
            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
            at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
            at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
            at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
            at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
            at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
            at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
            at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
            at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
            at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
            at com.example.spring.SpringTests5.test3(SpringTests5.java:35)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:498)
            at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
            at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
            at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
            at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
            at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
            at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
            at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
            at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
            at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
            at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
            at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
            at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
            at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
            at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
            at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
            at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
            at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
            at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)





          作者:skyline_wx

          出處:blog.csdn.net/WX10301075WX/article/details/119942805










          關(guān)注GitHub今日熱榜,專注挖掘好用的開發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開發(fā)者成長!







          點(diǎn)個在看,你最好看


          瀏覽 76
          點(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>
                  国产青青操| 天天躁夜夜躁狠狠躁AV | 中文字幕亚洲有码 | 久久一道本色 | 一级片网址 |