1.什么是循环依赖?

Bean A → Bean B → Bean A

在A对象生命周期中注入B,进入B生命周期找A,但A还不存在,继续进入A对象生命周期找B,这就是循环依赖。

2.循环依赖造成的结果

当Spring正在加载所有Bean时,Spring尝试以能正常创建Bean的顺序去创建Bean。这样会抛出异常。

┌─────┐
|  testA defined in class path resource [com/chuan/config/TestConfig.class]
↑     ↓
|  testB defined in class path resource [com/chuan/config/TestConfig.class]
└─────┘

3.逻辑分析

用代码看就是这样:但这只是普通的java代码,而不是spring的bean生命周期。

//实例化A
A a =new A();
//填充A属性,需要注入B,所以进入B的生命周期
B b =new B();
//b实例拿到了a的原始对象,注入进去
b.a=a;
//填充B属性
b.x=1;
//b生命周期结束,回到A的属性填充,填充A属性
a.b=b;
//填充A其他属性
a.x=1;

Bean的生成步骤如下(简化):

  1. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  2. 填充原始对象中的属性(依赖注入)
  3. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
  4. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可

暂时先一个demo分析一下怎么解决循环依赖:

demo1    A依赖于B,B依赖于A

A生命周期

1.实例化A(new A())--->a的原始对象--->存在一个map中

2.填充b属性-->从单例池里拿B对应的bean-->没拿到B--->创建B对应的Bean

3.填充其他属性

4.初始化后

5.添加到单例池

B生命周期

1.实例化B(new B())--->b的原始对象

2.填充a属性-->从单例池里拿a对应的bean--->找不到-->去这个map中找-->拿到a的原始对象

3.填充其他属性

4.初始化后

5.添加到单例池

这样来看两级循环就可以完成,一级缓存为单例池,二级缓存存生成的原始对象。

那为什么需要三级缓存呢?——只有两级缓存会存在代理对象和注入的对象不是同一个,这肯定是不合理的。

demo2

看一下demo这个流程:

A依赖于B、C

B依赖于A

C依赖于A

A生命周期

0.标记一下A正在进行创建

1.实例化A(new A())--->a的原始对象--->存入到第三级缓存

2.填充b属性-->从单例池里拿B对应的bean-->找不到-->创建B对应的Bean

3.填充c属性--->从单例池里拿C对应的bean-->找不到-->创建C对应的Bean

4.初始化后-》进行aop,从第二级缓存取出a代理对象

5.将代理对象添加到单例池(第一级缓存)

B生命周期

1.实例化B(new B())--->b的原始对象-->.........

2.填充a属性-->从单例池里拿a对应的bean--->找不到--》第二级缓存--->找不到---->发现a正在创建(也就是发现了需要循环依赖)---->第三级缓存---->执行lambda--->

进行aop--->获取a的代理对象---->存入到第二级缓存,并删除第三级缓存。

3.填充其他属性

4.初始化后

5.添加到单例池

C生命周期

1.实例化C(new C())--->c的原始对象-->.........

2.填充a属性-->从单例池里拿a对应的bean--->找不到--》第二级缓存--->获取到a代理对象(因为先执行了B,已经将第二级缓存缓存进去了)

3.填充其他属性

4.初始化后

5.添加到单例池

4.源码分析

在doCreateBean方法中,看这段代码:

    // 如果当前创建的是单例bean,并且允许循环依赖,并且还在创建过程中,那么则提早暴露boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 此时的bean还没有完成属性注入,是一个非常简单的对象// 构造一个对象工厂添加到singletonFactories中,这时只是存一下第三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));  // AService
//       addEarlySingleton(beanName, bean);}

只是将第三级缓存存进了singletonFactories里,并没有执行,因为它也不知道会不会出现循环依赖。

向下继续看,执行完属性填充和初始化后,一直到这。

if (earlySingletonExposure) {// 在解决循环依赖时,当a的属性注入完了之后,从缓存中中得到a AOP之后的代理对象Object earlySingletonReference = getSingleton(beanName, false);  // earlySingletonObjectsif (earlySingletonReference != null) {// 如果提前暴露的对象和经过了完整的生命周期后的对象相等,则把代理对象赋值给exposedObject// 最终会添加到singletonObjects中去if (exposedObject == bean) {exposedObject = earlySingletonReference;}// 如果提前暴露的对象和经过了完整的生命周期后的对象不相等// allowRawInjectionDespiteWrapping表示在循环依赖时,只能else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {// a的原始对象被注入给了其他bean,但是a最后被包装了// 也就是说其他bean没有用到a的最终版本throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] 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 " +"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");}}}
}

进入getSingleton方法查看,如何进行获取三级缓存中的内容。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {//从单例池(1级缓存)中拿beanObject singletonObject = this.singletonObjects.get(beanName);//没有从单例池获取到,并且这个单例正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 从earlySingletonObjects(二级缓存)中获取singletonObject = this.earlySingletonObjects.get(beanName);//没有从二级缓存中获取到,并且这个对象存在早期引用if (singletonObject == null && allowEarlyReference) {// 从singletonFactories(三级缓存)中获取ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//执行之前未执行的lambda表达式,获取到一个对象singletonObject = singletonFactory.getObject();  //将三级缓存的对象放入到二级缓存中,清除该beanName的三级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

5.特殊情况

有两个情况循环依赖是不能解决的:

1.两个都是原型bean也不能依赖

因为每次都是重新创建一个新的bean,所以出现循环 A->B->A->B->A

解决方法:将其中一个作用域改为单例

2.两个都使用构造方法注入也不行

因为第三级缓存是在实例化后进行的,所以在构造方法上注入,会导致三层缓存都找不到相应的bean,从而无限循环。

解决方法:在其中一个上加懒加载

6.总结

总结一下三级缓存:

  1. singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
  2. earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
  3. singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。

spring源码解析(五) 循环依赖相关推荐

  1. Spring源码 IOC和循环依赖AOP

    Spring源码 IOC和循环依赖AOP IOC篇 1.BeanFactory IOC容器,以BeanFactory为载体. BeanFactory,是Spring容器.这是由Spring管理,产生S ...

  2. Spring源码分析系列-循环依赖和三级缓存

    目录 循环依赖 多级缓存 一级缓存 二级缓存 当循环依赖遇上AOP 三级缓存 Spring三级缓存源码实现 总结 循环依赖   BeanFactory作为bean工厂管理我们的单例bean,那么肯定需 ...

  3. spring源码分析04-spring循环依赖底层源码解析

    1. 什么是循环依赖 很简单,就是A对象依赖了B对象,B对象依赖了A对象. // A依赖了B class A{public B b; }// B依赖了A class B{public A a; } 如 ...

  4. Spring源码解析(五)-Bean的实例化流程(上)

    在前面已经完成了对需要实例化bean的收集并封装成BeanDefinition,并且将BeanPostProcess等组件进行了提前实例化.接下来就到了容器启动的最后一步,也是最复杂的一步-实例化be ...

  5. spring源码解析五

    2019独角兽企业重金招聘Python工程师标准>>> 1.创建用于承载属性的BeanDefinition 这是一个接口,在spring中存在三种实现:RootBeanDefinit ...

  6. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  7. Spring 源码解析 - Bean创建过程 以及 解决循环依赖

    一.Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析,我们可以得到结论,资源文件中的 bean 定义信息,被组装成了 BeanDef ...

  8. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  9. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  10. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

最新文章

  1. C语言程序设计有哪几种结构,第章c语言程序设计的三种基本结构.ppt
  2. zabbix环境安装搭建
  3. 必 备 习 题 集 (五)
  4. mybatis支持属性使用驼峰的命名
  5. fiddler看ip地址_Fiddler查看IP和响应时间
  6. Yalmip最优化求解器+matlab | 教程(一)
  7. 网页视频播放器代码大全 + 21个为您的网站和博客提供的免费视频播放器
  8. Xcode 9中模拟器的位置
  9. 地图坐标的转换与说明
  10. python爬数据处理\ufeff、\xa0、\u3000的方法 (转载)
  11. 抢红包 作者 陈越单位 浙江大学
  12. IOS navigationController详解
  13. 互联网让FBI走下神坛
  14. @vue3 element-plus 按需引入,默认英文组件修改为中文
  15. position inherit 定位
  16. 选择靠谱的刷脸支付公司追风赶潮
  17. 读《Analyzing Unaligned Multimodal Sequence via Graph Convolution and Graph Pooling Fusion》
  18. dig是什么意思 java_dig的意思是挖掘,dig deep是什么意思呢?
  19. ejwt积分_菜鸡速通微积分:从十进制展开到数列、级数、幂级数、函数项级数...
  20. 网盘生意不好做,论坛也类似

热门文章

  1. 2021年安全员-B证(山东省)考试内容及安全员-B证(山东省)考试报名
  2. 爱--生活--名称解释
  3. 继Google AI黑人女性离职后又一女高管被解雇,A I伦理还有很长的路
  4. Python 二进制位运算
  5. Failed to convert value of type ‘java.lang.String‘ to required type ‘java.util.Date‘;
  6. 基于GeoHash算法的四大经纬度计算方案
  7. 超分辨率——基于SRGAN的图像超分辨率重建(Pytorch实现)
  8. 成都测试狗签约四川大学,捐赠50万助力科研教育事业发展
  9. 怎么用sanfengyun免费虚拟主机搭建自己的网站?
  10. 作为一名前端工程师,你需要学习哪些技术栈呢?