spring在单例,非构造方法注入的情况下允许循环依赖

1.循环依赖

a引用b,b引用a。a创建的时候需要b,但是b没有创建,需要先去创建b,b创建的时候又没有a,这就出现的循环依赖的问题

2.为什么单例,setter注入才能解决?

(1)构造器注入是在实例化对象时反射调用构造器去注入参数,所以既然beanA、beanB的都拿不到完整的依赖,就会进行无限的循环调用。setter注入方式 setter注入方式就是new出一个对象后,调用该对象的set方法对属性进行赋值。此时对象已经被new出来了,只不过是不完整而已。 如果出现了循环依赖的问题,这就要比构造器注入的方式好的多 所以Spring对于循环依赖问题的解决就是针对于setter方法的

(2)多例的bean是不需要放入到IOC容器中的

3.三级缓存

spring解决循环依赖主要是通过三级缓存

        // 用于存储完整的bean,接下来称之为【一级缓存】
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 用于存储不完整的bean,完成实例化,但是还未进行属性注入及初始化的对象,接下来称之为【二级缓存】
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); //提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象,接下来称之为【三级缓存】
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

4.基本流程

(1)当创建A对象的时候,会调用getBeanSington()方法,也就是去单例池(一级缓存)中查找有没有A的Bean,A还没创建成功,肯定没有。

(2)调用下一个getBeanSington()方法,判断A对象是否正在被创建,也就是去查找set集合中有没有A对象,这时候也没有,然后将A加入set集合中

(3)判断是否支持循环依赖,是否是单例,是否允许提前暴露。是的话将A实例(反射空参构造。只实例化,没有属性注入)放入singltonFactors三级缓存,其实这儿存的是一个工厂,后面就是通过这个工厂获取对象的

(4)A开始注入属性B

(5)流程和前面一样,这样的话三级缓存里就有了A和B

(8)B也开始注入属性A

(9)去单例池获取A的Bean,没有获取到

(10)去set集合中获取A对象,获取到了,说明A正在被创建

(11)依次去二级缓存,三级缓存获取A

(12)在三级缓存获取到A对象工厂,调用工厂的方法获取到A对象(如果A对象做了代理,就会在这儿进行代理),A对象放到二级缓存

(13)B对象注入属性A完成(此时A对象还没有完成属性注入,B只是持有A的一个引用),B进入单例池

(14)A对象此时就能从单例池中获取到B对象

(15)A对象完成初始化

         protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException { // 默认调用无参构造实例化Bean
// 构造方法的依赖注入,就是发生在这一步
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 实例化后的Bean对象,这里获取到的是一个原始对象,即没有进行属性填充的对象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass(); //...... // 解决循环依赖的关键步骤
// earlySingletonExposure:是否”提前暴露“原始对象的引用
// 因为不论这个bean是否完整,他前后的引用都是一样的,所以提前暴露的引用到后来也指向完整的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 如果需要提前暴露单例bean,则将该bean工厂放入【三级缓存】中
if (earlySingletonExposure) {
// 将刚创建的bean工厂放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
// 同样也会移除【二级缓存】中对应的bean,即便没有
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
} // Initialize the bean instance.
Object exposedObject = bean;
try {
//填充属性(依赖注入)
populateBean(beanName, mbd, instanceWrapper);
//调用初始化方法,完成bean的初始化操作
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//...... return exposedObject;
}

5.为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

【源码】spring循环引用的更多相关文章

  1. Spring 循环引用(二)源码分析

    Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环引用相关文章: & ...

  2. Spring 循环引用(三)源码深入分析版

    @ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...

  3. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  4. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  5. Spring 循环引用(一)一个循环依赖引发的 BUG

    Spring 循环引用(一)一个循环依赖引发的 BUG Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环 ...

  6. 3.2spring源码系列----循环依赖源码分析

    首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...

  7. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  8. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  9. 3.4 spring5源码系列--循环依赖的设计思想

    前面已经写了关于三篇循环依赖的文章, 这是一个总结篇 第一篇: 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 第二篇: 3.2spring源码系列----循环依赖源 ...

  10. 52、[源码]-Spring源码总结

    52.[源码]-Spring源码总结 总结 一.Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息: xml注册bean: 注解注册Bean:@Service.@Component ...

随机推荐

  1. C语言入门经典书目推荐--转

    国内良莠不齐的C语言教程数不胜数,同名如"C程序设计""C语言程序设计""C语言程序设计教程"的都多如牛毛,这些不知名的就不予考虑了,要看就 ...

  2. Apollo系列(一):分布式配置中心Apollo安装(Linux、Docker)

    一.介绍 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理 ...

  3. Redis小记(三)

    1.复制 通过slaveof命令或设置slaveof选项,实现一个服务器去复制另一个服务器,被复制的是主服务器,执行复制的是从服务器,复制过程中主从双方数据库保持数据一致 2.8版本以前,可分为初次复 ...

  4. DOS批处理中%cd%与%~dp0的区别详解

    转载:https://www.jb51.net/article/105325.htm DOS批处理中%cd%与%~dp0的区别详解     Windows下批处理中%cd%和%~dp0都能用来表示当前 ...

  5. 在程序开发中,++i 与 i++的区别在哪里?

    哈哈哈! 从大学开始又忘了...蜜汁问题哈 参考来源:https://www.zhihu.com/question/19811087/answer/80210083 i++ 与 ++i 的主要区别有两 ...

  6. apline无法向gitlab上传git lfs问题

    1 背景 在k8s中基于alpine做底层系统的容器进行git lfs push操作时,发现报错无法上传成功 Fatal error: Server error: http://git.ops.xxx ...

  7. Android Jetpack从入门到精通(深度好文,值得收藏)

    前言 即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第一篇. 记得去 ...

  8. go读取键盘输入两种方式

    一种scanf var x intfmt.Println("input a int number")fmt.Scan(&x)fmt.Printf("读取到内容:% ...

  9. linux(centos8):禁用selinux(临时关闭/永久关闭)

    一,selinux的用途 1,什么是selinux SELinux:即安全增强型 Linux(Security-Enhanced Linux) 它是一个 Linux 内核模块,也是 Linux 的一个 ...

  10. centos下安装mongodb 通过shell脚本

      #! /bin/bash yum -y update echo -e "开始安装mongodb\n" download_url=https://fastdl.mongodb.o ...