Spring 是如何解決循環(huán)依賴的,它支持哪種循環(huán)依賴?

結(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。
當(dāng)A初始化時,會觸發(fā)B的getBean操作,然后觸發(fā)C的getBean。
這時會調(diào)用A的objectFactory.getObject(),創(chuàng)建對象A的earlySingletonObject。
將對象A的earlySingletonObject添加到二級緩存的同時將A從三級緩存中移除。
這時C創(chuàng)建完畢。
對象B繼續(xù)DI,然后將二級緩存中已經(jīng)存在的A依賴注入進(jìn)來,對象B完成創(chuàng)建。
最后對象A創(chuàng)建完畢。
將二級緩存中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)個在看,你最好看
