Spring 是如何解决循环依赖的,它支持哪种循环依赖?
结论
先说结论:
单例bean可以通过@Autowrited进行循环依赖
单例bean不可以通过构造函数进行循环依赖
多例bean中不可以存在循环依赖
循环依赖是什么
当对象A中持有对对象B的引用,同时对象B中也持有对对象A的引用,就发生了循环依赖
spring是如何解决循环依赖问题的
通过前面两篇文章bean的实例化和注解收集和依赖注入的实现方式、如何阻止依赖注入和bean的初始化工作我们了解到spring将bean的创建分为实例化、依赖注入、初始化三个部分。只要实例化完成,那么这个bean对象就算是在堆中被创建出来了,这时相当于只是new了一个对象,但是里面的很多属性可能还没值(因为还没做DI)。spring之所以也可以在一定程度上解决循环依赖,也是得益于上面这个思想。
循环依赖代码
@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();
}
我们看下运行结果:
从结果中可以看到,CycleRefA和CycleRefB都成功的被加入到spring容器中了,并且,实现了A中有B,B中有A的效果
原理探究
我认为循环依赖只是我们给A引用B,B引用A这种现象的一种描述,实际上spring只是通过bean的实例化流程就解决了这个问题,并没有拉出一大段逻辑来特意处理循环依赖。所以,我们以上面的CycleRefA和CycleRefB为例,看看spring是如何初始化它们的。spring的getBean的逻辑大体如下:
借着上面这个图,我们说下CycleRefA是如何初始化的:
1、首先,当调用到getBean时,会通过调用getSingleton(beanName)尝试从缓存中获取CycleRefA的实例
2、因为CycleRefA还没有被创建,这时缓存里一定是没有的
3、然后就会调用到getSingleton(beanName,singletonFactory)来创建CycleRefA实例
4、开始创建CycleRefA的实例前,会将当前bean的beanName存到singletonsCurrentlyInCreation,主要是为了标记一下当前正在创建的bean
5、这时就会调用singletonFactory.getObject(),这里的singletonFactory其实是ObjectFactory接口,真正执行的是外面传入的createBean方法
6、通过无参的构造器实例化CycleRefA
7、将CycleRefA加入到三级缓存中
8、populateBean,对CycleRefA进行依赖注入
9、由AutowiredAnnotationBeanPostProcessor来处理@Autowrited注解,在依赖注入的过程中发现CycleRefA引用CycleRefB,所以触发了CycleRefB的getBean操作
10、跳过一大段前面重复的步骤…
11、这时,又来到了CycleRefB的populateBean
12、由于之前已经将CycleRefA的objectFactory放入到了三级缓存中,所以CycleRefB成功的获取到了CycleRefA
13、CycleRefB初始化完毕,将CycleRefB放入单例池中
14、CycleRefA依赖注入完毕
15、bean创建完毕后,调用afterSingletonCreation(beanName) 将当前bean从singletonsCurrentlyInCreation中移除
16、如果bean创建成功,那么调用addSingleton(beanName, singletonObject)将当前bean注册到单例池中,移除二级、三级缓存,同时注册到registeredSingletons中
17、CycleRefA被创建成功
总结
简单来说,spring处理循环依赖时用到了三个级别的缓存,所有的单例对象都要先从一级缓存也就是singletonObjects中取,如果一级缓存中没有,那就从二级缓(earlySingletonObjects)存中取,如果二级缓存中没有,那就从三级缓存(singletonFactories)中取,但是三级缓存缓存的是一个objectFactory,需要调用getObject()才能获取到对象,获取到对象后,将对象缓存到二级缓存中,同时移除这个bean的三级缓存。
为什么要有二级缓存?
在默认情况下,二级缓存是空的。spring本身不确定是不是真的会发生循环依赖,当一个对象进入到二级缓存后,这个对象一定已经发生循环依赖了。
三级缓存之所以是个objectFactory,是因为对象要提前暴露,但是如果有代理,需要提前将代理对象暴露出来。同时,这个暴露过程只能执行一次,所以会把objectFactory创建出来的对象缓存到二级缓存中。所以,按照我的理解,二级缓存更像是三级缓存的缓存。
在什么情况下会需要用到二级缓存
假如对象A引用了对象B,对象B引用了对象C和对象A,同时对象C又引用了对象A。
当A初始化时,会触发B的getBean操作,然后触发C的getBean。
这时会调用A的objectFactory.getObject(),创建对象A的earlySingletonObject。
将对象A的earlySingletonObject添加到二级缓存的同时将A从三级缓存中移除。
这时C创建完毕。
对象B继续DI,然后将二级缓存中已经存在的A依赖注入进来,对象B完成创建。
最后对象A创建完毕。
将二级缓存中A的earlySingletonObject移除。
反思与思考
spring解决循环依赖的思想
spring这套循环依赖的解决办法挺有意思的。大体的思想就是,只要你(bean)能被我创建出来,我就能解决你的循环依赖问题。因为在spring看来beanCreation分为创建内存对象(俗称new出来)、依赖注入、初始化三部分。但凡对象被new完毕,就相当于在内存区域已经占了一个坑了,就算里面还有很多属性没有被初始化,也不要紧了。
会不会存在earlySingletonObject与最后的singletonObject不一致的情况?
细思极恐的一件事:在存在循环依赖的情况下,如果A的earlySingletonObject与A最后的对象不是同一个对象,那A不就相当于多例了吗???比如在使用了AOP的情况下,会有问题吗?代码如下:
@Aspect
public class MyAspect1 {
/**
* 增强CycleRefC.hello,主要是为了让aop帮忙生成CycleRefC的代理
*/
@Before("execution(* com.example.spring.beans.CycleRefC.hello(..))")
public void beforeCHello() {
System.out.println("CycleRefC要开始sayHello了");
}
/**
* 增强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();
}
先看下结果:
嗯,结果没问题,白担心了,还以为发现了spring的惊天大bug…
不过,为什么呢???让我们接着debug探究一下why?
几个关键点的debug截图如下:
可以看到cycleRefC刚刚开始初始化,缓存里没有对象,果然,不出意料的走到了doCreateBean这里
从cycleRefC的populateBean,触发了cycleRefD的getBean
同样的,cycleRefD也是需要被创建的
因为cycleRefD中也持有了cycleRefC的引用,所以自然又一次发了cycleRefC的getBean,但是,这次不同了,三级缓存中已经有cycleRefC了。
不出意外的调用到了cycleRefC的getEarlyBeanReference
AbstractAutoProxyCreator作为aop的入口,做了两件事:1.缓存了被代理前的对象;2:创建代理对象并返回
所以cycleRefC的getEarlyBeanReference返回的是一个由CGLIB代理的对象
接下来cycleRefD完成了创建,视角会到cycleRefC的创建这里,此时,cycleRefC已经完成了initializeBean
但是,从截图上可以看到,exposedObject依然是cycleRefC,此时exposedObject已经和earlySingletonObject不一样了!!!spring用下面的方式解决了这个问题
当exposedObject == bean时,将earlySingletonReference赋值给exposedObject,这样就保证了earlySingletonReference与exposedObject是同一个对象。还有很重要的一点,之所以cycleRefC走完了initializeBean之后对象没有发生变化,原因在这里:
由于循环依赖的原因,cycleRefC被提前暴露了,所以当时earlyProxyReferences中记录了暴露前的对象,如果在中间的流程中,没有其他操作对bean进行内存地址的修改,那么这里就不会再次创建代理。然后,后面的事情就顺利成章了。
怎么才能让earlySingletonObject与最后的singletonObject真的不一致?
如果真的不一致,那spring真的会报错给你看…
我们先看下代码:
@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);
}
}
强制代理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
关注GitHub今日热榜,专注挖掘好用的开发工具,致力于分享优质高效的工具、资源、插件等,助力开发者成长!
点个在看,你最好看