【源码】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 ...
随机推荐
- Processing 高效控制管理图形方法(一)
之前在CSDN上发表过: https://blog.csdn.net/fddxsyf123/article/details/62456299
- Laver 文件版本遍历器
系统简介 最近有个需求,需要罗列出各个目录中文件的信息,检索各类文件的最新版本.网上看了很多方式,但发现没有合适的.于是利用空余时间开始编写了一套文件遍历系统,如此便有了Laver(紫菜).Laver ...
- python3-day3
一.函数基本语法及特性 重复用到的代码通过def封装起来,用到的时候直接调用函数名字:语法 1 def 函数名字(内容): 2 需要执行的动作 什么是函数: 函数一词来源于数学,但编程中的「函数」概念 ...
- IP地址的获取
//ip地址的获取:非原创,之前在其他地方看到,拿过来备份下: public static String getIPAddress(HttpServletRequest request) { Stri ...
- 开发基于Django和Websocket的堡垒机
WebSSH有很多,基于Django的Web服务也有很多,使用Paramiko在Python中进行SSH访问的就更多了.但是通过gevent将三者结合起来,实现通过浏览器访问的堡垒机就很少见了.本文将 ...
- Java知识系统回顾整理01基础04操作符01算术操作符
一.算数操作符类别 基本的有: + - * / % 自增 自减: ++ -- 二.基本算数操作符 + - * / 基本的加 减 乘 除 public class HelloWorld { public ...
- CSS中居中的完全指南(中英对照翻译)
翻译自:https://css-tricks.com/centering-css-complete-guide/ Centering things in CSS is the poster child ...
- DevOps元素周期表—2号元素Kibana
Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作.您可以使用 Kibana 对 Elasticsearch 索引 ...
- 这个网易云JS解密,老网抑云看了都直呼内行
最近更新频率慢了,这不是因为CK3发售了嘛,一个字就是"肝".今天来看一下网易云音乐两个加密参数params和encSecKey,顺便抓取一波某歌单的粉丝,有入库哦,使用mysql ...
- RFC 8684---TCP Extensions for Multipath Operation with Multiple Addresses
https://datatracker.ietf.org/doc/rfc8684/?include_text=1 TCP Extensions for Multipath Operation with ...