结论
先说结论:
- 单例bean可以通过@Autowrited进行循环依赖
- 单例bean不可以通过构造函数进行循环依赖
- 多例bean中不可以存在循环依赖
循环依赖是什么
当对象A 中持有对对象B 的引用,同时对象B 中也持有对对象A 的引用,就发生了循环依赖
spring是如何解决循环依赖问题的
通过前面两篇文章bean的实例化和注解收集和依赖注入的实现方式、如何阻止依赖注入和bean的初始化工作我们了解到spring将bean的创建分为实例化、依赖注入、初始化三个部分。只要实例化完成,那么这个bean对象就算是在堆中被创建出来了,这时相当于只是new了一个对象,但是里面的很多属性可能还没值(因为还没做DI)。spring之所以也可以在一定程度上解决循环依赖,也是得益于上面这个思想。
循环依赖代码
@Component
public class CycleRefA {
@Autowired
private CycleRefB cycleRefB;
public void hello(){
System.out.println("A:hello:"+cycleRefB);
}
}
@Component
public class CycleRefB {
@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 是如何初始化的:
-
首先,当调用到getBean 时,会通过调用getSingleton(beanName) 尝试从缓存中获取CycleRefA的实例 -
因为CycleRefA还没有被创建,这时缓存里一定是没有的 -
然后就会调用到getSingleton(beanName,singletonFactory) 来创建CycleRefA实例 -
开始创建CycleRefA的实例前,会将当前bean的beanName存到singletonsCurrentlyInCreation,主要是为了标记一下当前正在创建的bean -
这时就会调用singletonFactory.getObject() ,这里的singletonFactory其实是ObjectFactory接口,真正执行的是外面传入的createBean 方法 -
通过无参的构造器实例化CycleRefA -
将CycleRefA加入到三级缓存中 -
populateBean,对CycleRefA进行依赖注入 -
由AutowiredAnnotationBeanPostProcessor来处理@Autowrited注解,在依赖注入的过程中发现CycleRefA引用CycleRefB,所以触发了CycleRefB的getBean操作 -
跳过一大段前面重复的步骤… -
这时,又来到了CycleRefB的populateBean -
由于之前已经将CycleRefA的objectFactory放入到了三级缓存中,所以CycleRefB成功的获取到了CycleRefA -
CycleRefB初始化完毕,将CycleRefB放入单例池中 -
CycleRefA依赖注入完毕 -
bean创建完毕后,调用afterSingletonCreation(beanName) 将当前bean从singletonsCurrentlyInCreation中移除 -
如果bean创建成功,那么调用addSingleton(beanName, singletonObject) 将当前bean注册到单例池中,移除二级、三级缓存,同时注册到registeredSingletons中 -
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 {
@Before("execution(* com.example.spring.beans.CycleRefC.hello(..))")
public void beforeCHello() {
System.out.println("CycleRefC要开始sayHello了");
}
@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)
|