IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring入门——spring是如何解决循环依赖的,它支持哪种循环依赖? -> 正文阅读

[Java知识库]spring入门——spring是如何解决循环依赖的,它支持哪种循环依赖?

结论

先说结论:

  1. 单例bean可以通过@Autowrited进行循环依赖
  2. 单例bean不可以通过构造函数进行循环依赖
  3. 多例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中有BB中有A的效果

原理探究

我认为循环依赖只是我们给A引用B,B引用A这种现象的一种描述,实际上spring只是通过bean的实例化流程就解决了这个问题,并没有拉出一大段逻辑来特意处理循环依赖。所以,我们以上面的CycleRefACycleRefB为例,看看spring是如何初始化它们的。spring的getBean的逻辑大体如下:
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截图如下:
1
可以看到cycleRefC刚刚开始初始化,缓存里没有对象,果然,不出意料的走到了doCreateBean这里
2
从cycleRefC的populateBean,触发了cycleRefD的getBean
3
同样的,cycleRefD也是需要被创建的
4
因为cycleRefD中也持有了cycleRefC的引用,所以自然又一次发了cycleRefC的getBean,但是,这次不同了,三级缓存中已经有cycleRefC了。
5
不出意外的调用到了cycleRefC的getEarlyBeanReference
6
AbstractAutoProxyCreator作为aop的入口,做了两件事:1.缓存了被代理前的对象;2:创建代理对象并返回
7
所以cycleRefC的getEarlyBeanReference返回的是一个由CGLIB代理的对象
8
接下来cycleRefD完成了创建,视角会到cycleRefC的创建这里,此时,cycleRefC已经完成了initializeBean
9
但是,从截图上可以看到,exposedObject依然是cycleRefC,此时exposedObject已经和earlySingletonObject不一样了!!! spring用下面的方式解决了这个问题
10
当exposedObject == bean时,将earlySingletonReference赋值给exposedObject,这样就保证了earlySingletonReference与exposedObject是同一个对象。还有很重要的一点,之所以cycleRefC走完了initializeBean之后对象没有发生变化,原因在这里:
11
由于循环依赖的原因,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)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 08:57:09  更:2021-08-29 08:57:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:59:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码