Spring IoC循环依赖问题

什么是循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
在这里插入图片描述
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
Spring中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)

其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法

循环依赖处理机制

单例 bean 构造器参数循环依赖(⽆法解决)

prototype 原型 bean循环依赖(⽆法解决)

对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。
AbstractBeanFactory.doGetBean()⽅法:

if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName); 
protected boolean isPrototypeCurrentlyInCreation(String beanName) { 
Object curVal = this.prototypesCurrentlyInCreation.get(); 
return (curVal != null &&
        (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) 
curVal).contains(beanName))));
}

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记

try {
//创建原型bean之前添加标记 
beforePrototypeCreation(beanName); 
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args); 
}
finally {
//创建原型bean之后删除标记 
afterPrototypeCreation(beanName);
}

总结:Spring 不⽀持原型 bean 的循环依赖。

单例bean通过setXxx或者@Autowired进⾏循环依赖

Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中

循环依赖源码分析

在这里插入图片描述
在这里插入图片描述

(1)A实例化过程之后立马放入三级缓存(提前暴露自己)

refresh -> finishBeanFactoryInitialization-> getBean -> doGetBean -> createBean -> doCreateBean中

在这里插入图片描述

  • 判断A存在循环依赖,将A对象放入到三级缓存singletonFactories
	// 解决循环依赖的关键步骤,判断是否存在循环依赖:单例 && 允许循环依赖 && 当前bean正在创建中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		// 如果需要提前暴露单例Bean,则将该Bean放入三级缓存中
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// 将刚创建的bean放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
			//如果支持则暴露一个工厂
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			/**
			 * 如果单例池当中不存在才会add
			 * 因为这里主要为了循环依赖服务的代码
			 * 如果bean存在单例池的话其实已经是一个完整的bean了
			 * 一个完整的bean自然也是已经完成属性注入,循环依赖已经依赖上了
			 * 所以如果这个对象已经是一个完整的bean,就不需要关心,不需要进入 if
			 * 如果一级缓存中没有该实例,则放入三级缓存中
			 */
			if (!this.singletonObjects.containsKey(beanName)) {
				// 放入三级缓存
				this.singletonFactories.put(beanName, singletonFactory);
				/**
				 * 从三级缓存中remove掉当前的bean
				 * 为什么要remove?抛开细节,这三个map当中其实其实存的都是一个对象
				 * spring的做法是三个不能同时存在
				 */
				// 从二级缓存中移除
				this.earlySingletonObjects.remove(beanName);
				//将beanName注册到registeredSingletons缓存(已经注册的单例集合)
				this.registeredSingletons.add(beanName);
			}
		}
	}
  • A对象中需要进行属性注入,发现依赖了B对象

在这里插入图片描述
在这里插入图片描述

(2)B对象实例化过程和A实例化过程一样,将自己放入到三级缓存中之后下一步进行属性注入发现依赖于A,那么就去三级缓存使用尚未成型的Bean A,并将A升级放到二级缓存中。

  • B对象实例化放入三级缓存中

在这里插入图片描述

  • B对象中需要进行属性注入,发现依赖了A对象

在这里插入图片描述
在这里插入图片描述

(3) 再次加工Bean A的过程以及放入二级缓存示意图

在这里插入图片描述

(4)B对象创建完成之后放入一级缓存中

在这里插入图片描述
(5)A对象从一级缓存中获取到B对象,注入到A对象中
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5300.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个服务端同学的Vue框架入门及实践

做为服务端同学&#xff0c;接触前端代码较少&#xff0c;刚毕业的时候用过 jQuery Bootstrap2/3&#xff0c;当时的感觉就是&#xff0c;容易上手&#xff0c;学习门槛相对较低&#xff0c;另外就是有一个非常成熟的 jQuery 插件库&#xff0c;在这里&#xff0c;几乎可以找到…

vue集成tui.calendar日历组件

vue集成tui.calendar日历组件前言一、简介、效果图二、vue简单集成(集成js版本,没有使用官方的vue2版本)1.引包2.简单示例三、自定义功能1.需求分析、效果展示2.实现思路前言 vue2的集成在git上官方已经给出了demo这里就不贴代码了。本次主要是vue3集成 最近有个功能需要一个日…

重发布实验

基础配置&#xff1a; [r1]int l0 [r1-LoopBack0]ip add 1.1.1.1 24 [r1-LoopBack0]int g0/0/0 [r1-GigabitEthernet0/0/0]ip ad 192.168.12.1 24 [r1-GigabitEthernet0/0/0]int g0/0/1 [r1-GigabitEthernet0/0/1]ip add 192.168.123.1 24 [r1]ospf 1 router-id 1.1.1.1 [r1-o…

自学大数据第12天~Hbase

先留个问题~ERROR: KeeperErrorCode ConnectionLoss for /hbase/master 稍后解决 找到了问题的根因: 查看报错日志 事关tmp文件夹的配置,所以去找一下hbase配置文件中关于这个文件夹的配置项 我的策略是将这个配置项注销掉 然后启动hbase ,之后hmaster就成功启动了; 接着s…

熟练Redis之无处不在的锁

为了保证并发访问的正确性&#xff0c;Redis提供了两种方法,分别是加锁和原子操作 Redis加锁两个问题:一个是&#xff0c;如果加锁操作多&#xff0c;会降低系统的并发访问性能;第二个是&#xff0c;Redis客户端要加锁时&#xff0c;需要用到分布式锁&#xff0c;而分布式锁实…

Coremail奇安信发布2022中国企业邮箱安全性研究:应对ChatGPT带来的安全挑战

日前&#xff0c;广东盈世科技计算机有限公司与奇安信集团联合编写发布《2022中国企业邮箱安全性研究报告》。 报告数据显示&#xff1a;2022年&#xff0c;全国企业邮箱用户共收到各类钓鱼邮件约425.9亿封&#xff0c;相比2021年收到各类钓鱼邮件的342.2亿封增加了24.5%。 一…

华为OD机试用java实现 -【RSA 加密算法】

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:RSA 加密算法 题目 RSA 加密…

【愚人节专场】Java实现定时发送小情话

首先&#xff0c;感谢大佬的帮助~附上大佬的博客以示尊敬https://blog.csdn.net/qq_38591577/article/details/128164308?spm1001.2014.3001.5502 功能实现&#xff1a; 在名为愚人节&#xff0c;实为告白/情人节的日子里&#xff0c;怎么样才能引起TA的关注呢&#xff1f;不…

49天精通Java,第21天,Java内部类,看看文心一言、ChatGPT怎么说

目录文心一言谈Java内部类ChatGPT谈Java内部类下面来聊聊哪吒的见解。一、为什么需要内部类&#xff1f;二、内部类分为四种三、成员内部类1、什么是成员内部类2、代码实例3、成员内部类进阶代码实例4、控制台显示5、外部类访问内部类四、局部内部类五、匿名内部类1、匿名内部类…

Dragonfly 最新正式版本 v2.0.9 已经发布!

作者&#xff1a;戚文博-蚂蚁集团 Dragonfly 最新正式版本 v2.0.9 已经发布&#xff01;感谢 Dragonfly 的贡献者们&#xff0c;同时也感谢默默支持 Dragonfly 项目的各个公有云团队。欢迎访问 d7y.io [ 1] 网站来了解详情&#xff0c;下面具体介绍 v2.0.9 版本带来了那些更新。…

【Redis】十大数据类型(下篇)

文章目录redis位图(bitmap) --- 底子还是string基本命令图示setbit key offset value setbit 键 偏移位 只能零或者1getbit key offset 查看获取字符串长度 strlen统计key中包含1的个数 bitcount keybitop 统计两个比特key是否都为1技术落地&#xff1a;打卡签到&#xff0c;频…

【C语言蓝桥杯每日一题】——等差数列

【C语言蓝桥杯每日一题】——等差数列&#x1f60e;前言&#x1f64c;等差数列&#x1f64c;解题思路分析&#xff1a;&#x1f60d;解题源代码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&…

让ChatGPT帮我写shell脚本, 结局很感人

七问ChatGPT, 剑指shell脚本编写 step1: 初问step2: 再问step3: 三问step4: 四问step5: 五问step6: 问个derstep7: 解决问题step8: 小问一下关于ChatGPT思考昨天浏览一篇关于脚本的技术文章的时候, 偶然看见一篇文章中写道关于mysql备份的脚本. 但是这个脚本时基于本地的MySQL服…

Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource

前言&#xff1a;对于同一个系统&#xff0c;不同的租户需要自己独立分隔的数据库&#xff08;每个数据库的表结构可以是相同的&#xff09;&#xff0c;同时也要支持跨数据源的查询&#xff1b;并且支持分布式事务&#xff0c;如果这里不使用分库分表插件&#xff0c;需要怎样…

使用dd复制将乌班图系统(Ubuntu22.04)完整迁移到新硬盘并扩容

我的折磨历程 开始的时候用乌班图的时候&#xff0c;不懂事&#xff0c;根目录太小了&#xff0c;后来就满了&#xff0c;就就感觉完全没法用&#xff0c;看着现在硬盘贼便宜&#xff0c;去狗东买了个新的硬盘。感觉挂载硬盘并不能解决我的问题&#xff0c;最后选择了保留系统数…

ython和PyTorch实现ChatGPT批量AI智能写作

怎么实现用chatgpt批量写作 ChatGPT是一种针对文本生成的自然语言处理工具&#xff0c;它可以用于生成大量的文本内容。但是&#xff0c;由于ChatGPT需要的计算资源较大&#xff0c;处理时间较长&#xff0c;因此在批量写作时需要考虑花费的时间和资源。 以下是一些步骤&…

又一个免费GPT-4工具 Cursor,程序员写代码将被颠覆

每天都被openai震撼到&#xff0c; 他们家被广为人知的产品是chatgpt&#xff0c;就是那个聊天工具。现在已经开始有越来越多的产品集成openai&#xff0c;比如微软的office&#xff0c;bing。现在又一个工具出现&#xff0c;一个叫Cursor的编辑器已经集成了openai的GPT-4&…

Spring系列(六) --- SpringBoot 与 Servlet 的比较及 Spring 读取配置文件的方式

SpringSpringBoot VS ServletSpring 读取配置文件的方式yml 和 properties 的区别SpringBoot VS Servlet Spring 读取配置文件的方式 1 Value 注解获取单个配置项 如在 yml 中定义一个 qq 音乐的 token; 然后输出, 如下: 2 针对对象的读取: ConfigurationProperties 在 yml 中…

YOLOv5添加辅助训练头

1. 介绍 思路 添加 Aux head 的主要原因是让网络中间层学到更多信息,有更丰富的梯度信息帮助训练。这里要注意,好的梯度信息能够让相同参数量的网络学的更好。 作者原文为: By letting the shallower auxiliary head directly learn the information that lead head has l…

【C#基础】泛型的概念?有什么例子?在游戏中有什么可以使用的地方?

概念 让chatGpt来为我们讲解。 在C#中&#xff0c;泛型是一种允许开发人员编写可重用代码&#xff0c;可以处理多种数据类型的特性。 使用泛型&#xff0c;可以创建类、方法、接口和委托这种不属于任何特定数据的类型&#xff0c;但可以处理满足某些约束条件的任何数据类型。…