Spring 是如何解决循环依赖的,它支持哪种循环依赖?

程序员考拉

共 11789字,需浏览 24分钟

 ·

2021-09-03 01:26

公众号关注 “GitHub今日热榜
设为 “星标”,带你挖掘更多开发神器!







结论


先说结论:


  • 单例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。


  1. 当A初始化时,会触发B的getBean操作,然后触发C的getBean。

  2. 这时会调用A的objectFactory.getObject(),创建对象A的earlySingletonObject。

  3. 将对象A的earlySingletonObject添加到二级缓存的同时将A从三级缓存中移除。

  4. 这时C创建完毕。

  5. 对象B继续DI,然后将二级缓存中已经存在的A依赖注入进来,对象B完成创建。

  6. 最后对象A创建完毕。

  7. 将二级缓存中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今日热榜,专注挖掘好用的开发工具,致力于分享优质高效的工具、资源、插件等,助力开发者成长!







点个在看,你最好看


浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报