Spring循环依赖问题——从源码画流程图

news2024/11/17 13:45:36

文章目录

    • 关键代码
    • 相关知识
      • 为什么要使用二级缓存
      • 为什么要使用三级缓存
      • 只使用两个缓存的问题
      • 不能解决构造器循环依赖
      • 为什么多例bean不能解决循环依赖问题
      • 初始化后代理对象赋值给原始对象
      • 解决循环依赖
      • SpringBoot开启循环依赖



循环依赖 在线流程图

在这里插入图片描述



关键代码

从缓存中查询getSingleton()方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    // 一级缓存 二级缓存中取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                // 加锁后再查询 重新取一遍
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

经过单例Bean判断和是否允许开启依赖注入判断后,往第三级缓存中存ObjectFactory函数式接口对象

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");
    }
    // 循环依赖-添加到三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

初始化后将循环依赖产生的代理对象赋值给普通对象

	if (earlySingletonExposure) {
        // 从二级缓存中取代理对象
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
                // 赋值给当前普通对象
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						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 " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}



相关知识

案例:A对象 与 B对象互相引用,A对象 与 C对象互相引用。



为什么要使用二级缓存

作用:

  • 二级缓存中存储的是不完整的早期的bean。
  • 二级缓存使不完整的bean和完整的bean分开存储,保证并发线程安全,提高性能

假如不使用二级缓存,只有一级缓存的情况。一级缓存它想要打破循环的话就只能把半成品Bean存放在一级缓存中,这样创建B对象时就获取到一级缓存中的半成品A对象了,这样也就打破了对象互相引用的死循环。

问题是多线程访问时,其他线程可能就拿到了A对象取使用了。

解决方法是对整个getBean()方法加锁,这样的问题是锁的粒度太大了,而且想获取已经创建好了的对象也需要等待

所以就加入了二级缓存,用二级缓存来降低锁的粒度,提升性能,



为什么要使用三级缓存

作用:

  • 解决循环依赖的死循环
  • 使用ObjectFactory函数式接口,提升bean创建过程扩展性,保证规范,代码职责单一性

如果不使用三级缓存,同时A对象要进行AOP的话,此时就会出现依赖注入给B对象中的属性的A的普通对象,A经过创建过程之后存入单例池的却是代理对象

如果想要解决上面的问题就需要在创建bean对象的过程中,在实例化之后,属性填充之前就进行AOP操作生成代理对象,并存入二级缓存中。

这种方式就破坏了Bean创建过程的规范,同时还会出现循环依赖多次创建多次动态代理对象。

所以解决方法是我只判断出现了循环依赖这种情况才对该bean提前进行AOP操作。这些操作代码我不能加在创建bean的各个小步骤中,为了保证代码职责单一性。所以便有了三级缓存来保存ObjectFactory函数式接口对象,在出现了循环依赖的情况下就去执行对应方法。



只使用两个缓存的问题

如果只使用一级和二级缓存,那么就需要考虑AOP问题,可能给对象B注入的是普通对象,存入单例池是的代理对象

如果只使用一级和三级缓存,A对象与B对象互相引用,A对象与C对象互相引用。在执行行A对象就会创建两个AOP代理对象分别注入给B和C



不能解决构造器循环依赖

因为在创建Bean的总流程中,先进行实例化,完成之后才会去往三级存储中存入数据。此时实例化都没有完成,普通对象都没有创建成功,所以无法处理

解决方法是使用@Lazy注解



为什么多例bean不能解决循环依赖问题

我们从源码中是可以发现,再往三级缓存中存数据前是进行了if判断的,只有单例bean才会加入到三级缓存中。

其实解决循环依赖核心就是使用了一个Map,而这个map就相当于一个缓存。

我们bean是单例的,而且注入是通过setter字段注入的,单例意味着只需要创建一次对象,后续就可以从缓存中取出来,字段注入意味着我们无需调用构造方法进行注入。

  • 如果是原型Bean,那么意味着每次都要去创建bean,无法利用缓存
  • 如果是构造方法注入,那么意味着需要调用构造方法注入,也无法利用缓存



初始化后代理对象赋值给原始对象

在bean的创建过程中,还有一个地方跟循环依赖有关。

如果A是动态代理对象,通过三级缓存循环依赖之后,就会出现B.a = A的代理对象。但进行了初始化整个过程的A对象是普通对象,所以此时就需要把二级缓存中的代理对象赋值给BeanA。

	if (earlySingletonExposure) {
        	// 1. 从二级缓存中取出代理对象
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
                // 2. 赋值给普通对象
                // 这里会判断初始化过程中是否改变了当前对象,如果改变了则下面的if判断不成立
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						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 " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
  • @Async注解会在初始化过程中创建动态代理对象,导致上面if判断中得到的代理对象和原始bean对象不一致。故而抛出异常
  • @Transactional注解也是代理对象,为什么不会报错?这是因为@Transaction注解的代理对象和AOP实际上是一个代理对象。

在解决循环依赖时,我就通过了普通对象创建了一个代理对象,解决你在初始化过程中有改变了普通对象,那我之前创建的代理对象岂不是没用了。



解决循环依赖

如果关闭了循环依赖功能,如果有两种方式解决循环依赖

  1. 使用@Lazy注解加在属性上,代表Spring容器加载时注入,会临时注入一个代理对象,等真正使用的时候再通过代理对象去调用最终的getBean()方法
  2. 添加一个中间类, 中间类去依赖A\B, 然后让中间类去组织他们的依赖方法

在这里插入图片描述



SpringBoot开启循环依赖

不建议开启,能产生循环依赖问题的代码本身就是一种不规范的设计。

Spring作者都已表示可能会在后续版本去掉循环依赖支持。 除非你是把 Spring代码移植到SpringBoot ,可以考虑开启循环依赖已保证之前代码正常性。

spring.main.allow-circular-references=true

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙开发系统基础能力:【@ohos.hiTraceChain (分布式跟踪)】

分布式跟踪 本模块提供了端侧业务流程调用链跟踪的打点能力&#xff0c;包括业务流程跟踪的启动、结束、信息埋点等能力。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import hi…

固特超声波清洗机怎么样?三大超声波清洗机美的、固特、希亦谁更好?

眼镜是我们日常生活中不可或缺的用具&#xff0c;但随着使用时间的增长&#xff0c;眼镜上的灰尘和污垢也会逐渐积累&#xff0c;传统的清洗方法往往难以彻底清洁。为了解决这一难题&#xff0c;超声波清洗机出现了&#xff01;它利用超声波振动原理&#xff0c;可以轻松、快速…

群辉NAS中文件下载的三种方案

目录 一、迅雷套件 1、添加套件来源 2、安装套件 3、手机安装迅雷 二、qBittorrent套件 1、添加套件来源 2、改手工安装 3、更新后的问题 4、最后放弃DSM6 (1)上传文件手工安装 (2)添加套件来源 5、解决登陆报错 6、添加tracker 7、修改下载默认位置 8、手机…

【十一】【QT开发应用】模拟腾讯会议登录界面设计UI

ui 加入会议的样式表 QPushButton { /* 前景色 */ color:#0054E6; /* 背景色 */ background-color:rgb(255,255,255); /* 边框风格 */ border-style:outset; /* 边框宽度 */ border-width:0.5px; /* 边框颜色 */ border-color:gray; /* 边框倒角 */ border-radius…

python自动化测试之DDT数据驱动的实现代码

时隔已久&#xff0c;再次冒烟&#xff0c;自动化测试工作仍在继续&#xff0c;自动化测试中的数据驱动技术尤为重要&#xff0c;不然咋去实现数据分离呢&#xff0c;对吧&#xff0c;这里就简单介绍下与传统unittest自动化测试框架匹配的DDT数据驱动技术。 话不多说&#xff…

神经网络的编程基础

神经网络的编程基础 二分类 二分类是机器学习中的一种基本分类问题&#xff0c;其中每个样本被划分为两个类别中的一个&#xff0c;即正类或负类。这种分类问题在现实生活中有广泛的应用&#xff0c;例如判定邮件是否为垃圾邮件、判别某个人是否患病等。二分类模型根据样本的特…

Python高压电容导电体和水文椭圆微分

&#x1f3af;要点 &#x1f3af;二维热传导二阶偏微分方程 | &#x1f3af;调和函数和几何图曲率 | &#x1f3af;解潮汐波动方程 | &#x1f3af;解静止基态旋转球体流体运动函数 | &#x1f3af;水文空间插值 | &#x1f3af;流体流动模拟求解器 | &#x1f3af;随机算法解…

光伏半导体的种类

光照射半导体材料时&#xff0c;其电导率发生变化的实质是光生载流子的产生。在半导体中&#xff0c;价带中的电子受到一定能量的光子激发后&#xff0c;可以跃迁到导带&#xff0c;形成自由电子和空穴对&#xff0c;即光生载流子。这些光生载流子会增加半导体的导电能力&#…

NepnepxCATCTF Pwn-Chao

文章目录 参考类型混淆异常处理的栈回退机制虚表和类的恢复假想的程序结构逆向工程场景步骤解析 idabug检查找虚表strupsc_str()alloca异常逆向maindisplayupdatecreate 新东西exp和思路 参考 https://www.cnblogs.com/winmt/articles/17018284.html 类型混淆 关于C中由虚函…

【乐吾乐2D可视化组态编辑器】图表动态显示

1. 添加数据 乐吾乐2D可视化组态编辑器地址&#xff1a;https://2d.le5le.com/ 图表动态展示是指一个图表图元的数据属性&#xff08;一般是dataY&#xff09;绑定多个变量&#xff0c;建立通信后数据动态变化展示。 官网默认Echarts图表拖拽到画布中是已经添加了图元的da…

【SpringCloud-Seata源码分析3】

文章目录 事务的提交客户端提交流程服务端提交流程客户端删除undo_log 事务回滚客户端事务回滚服务端回滚事务 事务的提交 前面两篇我们分析了seata的TC初始化和TM,RM初始化&#xff0c;并且事务准备阶段源码及业务Sql执行&#xff0c;下面我们分析事务的提交源码。 客户端提…

【面试题】等保(等级保护)的工作流程

等保&#xff08;等级保护&#xff09;的工作流程主要包括以下几个步骤&#xff0c;以下将详细分点介绍&#xff1a; 系统定级&#xff1a; 确定定级对象&#xff1a;根据《信息系统等级保护管理办法》和《信息系统等级保护定级指南》的要求&#xff0c;确定需要进行等级保护的…

Charles抓包工具系列文章(二)-- Repeat 回放http请求

一、什么是http请求回放 当我们对客户端进行抓包&#xff0c;经常会想要重试http请求&#xff0c;或者改写原有部分进行重新请求&#xff0c;都需要用到回放http请求。 还有一种场景是压力测试&#xff0c;对一个请求进行重复请求多少次&#xff0c;并加上适当的并发度。 这里…

2024年6月22日,雨中骑行谷仓坝游后记。 (校长骑行撰稿)

在这个快节奏的时代&#xff0c;生活中总是充满了无尽的压力和喧嚣。然而&#xff0c;骑行就像一股清流&#xff0c;给人们带来片刻的宁静和思考&#xff0c;是一种独特的释放方式。它不仅是一种锻炼&#xff0c;更是一种探索世界、理解生活的方式。这次&#xff0c;我们校长骑…

[职场] 线上面试的准备工作 #知识分享#经验分享#媒体

线上面试的准备工作 面对求职中的面试&#xff0c;应届毕业生该做些什么准备呢&#xff1f;在这里&#xff0c;向各位分享面试前做好预案不慌张几点准备。现在许多面试是通过线上形式进行的。对于求职者来说&#xff0c;要做好两手准备。在这里&#xff0c;重点与大家分享线上面…

pyhon模块以及常用的第三方模块

import my_info as info print(info.name) info.show()from my_info import * print(name) show() pyhon中包的导入 import admin.my_admin as ad # 包名.模块名 admin是包名&#xff0c;my_admin是模块名print(ad.name) print(ad.info())from admin import my_admin as ad # …

【可控图像生成系列论文(三)】北大 Context-Aware Unsupervised Text Stylization论文解读1

【可控图像生成系列论文&#xff08;一&#xff09;】 简要介绍了论文的整体流程和方法&#xff1b;【可控图像生成系列论文&#xff08;二&#xff09;】则将就整体方法、模型结构、训练数据和纹理迁移进行了更详细的介绍。 本篇将介绍来自 ACM MM 2018 的一篇字体风格化的可控…

【43 Pandas+Pyecharts | 京东某商品销量数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 查看数据描述信息 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 销量(瓶)地图分布3.2 每月销量(瓶)3.3 男性女性购买数量…

《看不影子的少年》一部探讨偏见与接纳的电视剧❗

《看不见影子的少年》这部电视剧以其独特的视角和深刻的主题 给我留下了深刻的印象。该剧讲述了一位与众不同的少年 他无法在阳光下留下影子&#xff0c;象征着他在社会中的孤独与不被理解 观看过程中&#xff0c;可以感受到少年内心的挣扎与渴望 他渴望被接纳&#xff0c;渴…

【linux kernel】一文总结linux输入子系统

文章目录 一、导读二、重要数据数据结构&#xff08;2-1&#xff09;struct input_dev&#xff08;2-2&#xff09;input_dev_list和input_handler_list&#xff08;2-3&#xff09;struct input_handler 三、input核心的初始化四、常用API五、输入设备驱动开发总结(1)查看输入…