【源码】spring循环引用
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循环引用的更多相关文章
- Spring 循环引用(二)源码分析
Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环引用相关文章: & ...
- Spring 循环引用(三)源码深入分析版
@ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...
- Spring IOC 容器源码分析 - 循环依赖的解决办法
1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...
- Dubbo 源码分析 - 服务引用
1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...
- Spring 循环引用(一)一个循环依赖引发的 BUG
Spring 循环引用(一)一个循环依赖引发的 BUG Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环 ...
- 3.2spring源码系列----循环依赖源码分析
首先,我们在3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 中手写了循环依赖的实现. 这个实现就是模拟的spring的循环依赖. 目的是为了更容易理解spring源码 ...
- 5.2 Spring5源码--Spring AOP源码分析二
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 3.4 spring5源码系列--循环依赖的设计思想
前面已经写了关于三篇循环依赖的文章, 这是一个总结篇 第一篇: 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖 第二篇: 3.2spring源码系列----循环依赖源 ...
- 52、[源码]-Spring源码总结
52.[源码]-Spring源码总结 总结 一.Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息: xml注册bean: 注解注册Bean:@Service.@Component ...
随机推荐
- 树莓派3B+安装64位ubuntu系统和docker工具
想在树莓派3B上安装一些64位应用(例如64位JDK),因此首先要安装64位的操作系统,今天咱们就一起来实战: 原文地址:https://blog.csdn.net/boling_cavalry/ar ...
- python学习笔记1之-python简介及其环境安装
python学习笔记之-python简介及其环境安装 最近几年python之火不用多说,最近开始利用时间自学python,在学习的过程中,按照自己的思路和理解记录下学习的过程,并分享出来,如果正好你也 ...
- Android小部件Widget开发过程中的坑和总结
@ 目录 概述 官方参考 效果图 AndroidManifest.xml Receiver Service Options res/xml/ widget_desktop_options.xml 常用 ...
- Python-字符编码-Unicode UTF-8
什么是字符编码? --世界上有很多国家,每个国家都有自己独特的语言,所以在计算机普及的当今世界, 每个国家都有自己的字符编码,本国的软件运行在其他国家的机器上,会出现乱码, 有utf-8,gbk等各种 ...
- LRU Cache & Bloom Filter
Cache 缓存 1. 记忆 2. 空间有限 3. 钱包 - 储物柜 4. 类似背代码模板,O(n) 变 O(1) LRU Cache 缓存替换算法 1. Least Recently Use ...
- Spring循环依赖的问题
什么是循环依赖?就是两个Bean相互引用,比如用@Autowire 相互注入. 那么Spring是如何解决这个问题的呢?在Bean还未完全实例化前(类只实例化了一部分),将bean提前暴露出来 ...
- sprintf_s() 、sprintf()和printf()区别和用法
转载:https://blog.csdn.net/qq_35608277/article/details/80878802 int sprintf_s(char *buffer,size_t size ...
- 【题解】[USACO13FEB]Tractor S
题目戳我 \(\text{Solution:}\) 好久没写啥\(dfs\)了,借这个题整理下细节. 观察到答案具有二分性,所以先求出其差的最大最小值,\(\log val\)的复杂度不成问题. 考虑 ...
- 【学习笔记】Polya定理
笔者经多番周折终于看懂了\(\text{Burnside}\)定理和\(\text{Polya}\)定理,特来写一篇学习笔记来记录一下. 群定义 定义:群\((G,·)\)是一个集合与一个运算·所定义 ...
- springCloud项目搭建
新建父maven项目 groupId:pers.xzp.springCloudartifactId:springCloud 父项目中仅仅需要一个pom文件,用于管理模块的依赖统一.继承等 编辑pom文 ...