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

文章目录

    • 关键代码
    • 相关知识
      • 为什么要使用二级缓存
      • 为什么要使用三级缓存
      • 只使用两个缓存的问题
      • 不能解决构造器循环依赖
      • 为什么多例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.mzph.cn/bicheng/34408.shtml

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

相关文章

【贪心算法初级训练】在花坛上是否能种下n朵花、碰撞后剩余的行星

1、在花坛上是否能种下n多花 一个很长的花坛&#xff0c;一部分地已经种植了花&#xff0c;另一部分却没有&#xff0c;花不能种植在相邻的地块上否则它们会争夺水源&#xff0c;两者都会死去。给你一个整数数组表示花坛&#xff0c;由若干个0和1组成&#xff0c;0表示没种植花…

51单片机STC89C52RC——7.1 串口通信

目的/效果 实现单片机串口与电脑串口工具进行数据通讯&#xff0c; 1&#xff1a;设备向电脑串口发送HEX 2&#xff1a;让电脑串口工具控制单片机LED亮灭。同时让单片机反馈控制的结果。 一&#xff0c;STC单片机模块 二&#xff0c;串口通讯 2.1 串行通信与并行通信 &…

axios全局封装AbortController取消重复请求

为什么&#xff1f; 问题&#xff1a;为什么axios要配置AbortController&#xff1f;防抖节流不行吗&#xff1f; 分析&#xff1a; 防抖节流本质上是用延时器来操作请求的。防抖是判断延时器是否存在&#xff0c;如果存在&#xff0c;清除延时器&#xff0c;重新开启一个延…

win10改远程桌面端口,Windows 10 修改远程桌面端口号的专业指南

在Windows 10系统中&#xff0c;远程桌面&#xff08;Remote Desktop&#xff09;功能允许用户从一台计算机远程访问和控制另一台计算机。为了增加远程连接的安全性&#xff0c;减少潜在的安全风险&#xff0c;修改默认的远程桌面端口号是一个常见的安全措施。以下是在Windows …

k8s学习--YAML资源清单文件托管服务nginx

文章目录 前言应用环境具体实现步骤1.安装源码nginx及相关模块2.修改nginx配置文件3.启动验证4.测试 总结 前言 nginx 是一个开源的高性能 HTTP 和反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。在容器和 Kubernetes 的背景下&#xff0c;nginx 经常被用作静…

决策树算法原理

目录 一&#xff1a;介绍 二&#xff1a;算法原理 1.熵和信息熵 2.信息增益 三决策树分裂指标 1.信息熵分裂&#xff1a; 2.Gini系数&#xff08;CART&#xff09; 3.信息增益率 一&#xff1a;介绍 决策树( Decision Tree) 又称为判定树&#xff0c;是数据挖掘技术中的…

你如何看待市场波动性的?

实际上&#xff0c;波动性并不总是负面的&#xff0c;它有时也孕育着快速获利的机会。 对于长期投资者而言&#xff0c;市场波动&#xff08;尤其与熊市相伴时&#xff09;往往是一个优势。它允许投资者拓展并多样化投资组合&#xff0c;以较低的价格购入投资工具&#xff0c;…

【嵌入式Linux】<总览> 多进程(更新中)

文章目录 前言 一、进程的概念与结构 1. 相关概念 2. 内核区中的进程结构 3. 进程的状态 4. 获取进程ID函数 二、进程创建 1. fork和vfork函数 2. 额外注意点 3. 构建进程链 4.构建进程扇 三、进程终止 1. C程序的启动过程 2. 进程终止方式 四、特殊的进程 1. 僵…

免费体验软件开发生产线 CodeArts

软件开发生产线 CodeArts 一站式、全流程、安全可信的软件开发生产线&#xff0c;开箱即用&#xff0c;内置华为多年研发最佳实践&#xff0c;助力效能倍增和数字化转型 免费试用体验版套餐&#xff0c;50人内免费试用 功能特性 Scrum和看板需求模型 代码托管 代码检查&am…

GIS开发如何高质量就业?这几点是关键!

高质量就业&#xff0c;包含薪资和其他福利待遇&#xff0c;在讨论如何高质量就业之前&#xff0c;我们先来看下GIS开发岗位的前景、薪资水平如何&#xff1f;最后讨论一下GIS开发工程师到底需要学习哪些技术&#xff1f; 01 GIS开发岗位呈持续上升趋势 从GIS开发岗位趋势也可…

Java知识点整理 11— 后端 Spring Boot 万用初始化模板使用

一. 模块简介 annotation&#xff1a;自定义注解aop&#xff1a;请求日志和权限校验common&#xff1a;通用类config&#xff1a;配置类constant&#xff1a;常量 controller&#xff1a;控制层esdao&#xff1a;方便操作ESexception&#xff1a;异常类job&#xff1a;定时任务…

Facebook广告投放的6个误区,老手也会犯

一、没有目标 无论是投放哪种产品&#xff0c;我们始终都需要明确&#xff0c;广告的目标是什么。 因为Facebook广告的形式和类型&#xff0c;也经常会有变化&#xff0c;例如近期Facebook推出的360视频广告&#xff0c;以及之后即将推出的LIVE&#xff0c;Mid-Roll视频插播广…

美国电商选品、大促、趋势、案例,掌慧科技首期NewsBreak沙龙干货满满

今年第一季度&#xff0c;美国电商销售额达到了2681.2亿美元&#xff0c;相较上一年同期的2471.8亿美元增长8.5%。同时&#xff0c;该季度美国电商销售额在零售业总销售额中的占比为22.2%&#xff0c;高于上一年同期的21.2%。美国在2023年下半年通胀得到良好控制&#xff0c;20…

CleanMyMac2024破解版下载链接!你的Mac清洁利器!

嘿&#xff0c;亲爱的朋友们&#xff0c;今天我要跟大家分享一款我最近超级依赖的电脑清理神器—CleanMyMac2024破解版&#xff01;如果你还在为电脑运行缓慢、存储空间不够而烦恼&#xff0c;那你一定不能错过它&#xff01; &#x1f525; 为什么选择CleanMyMac2024破解版&am…

声波的种类

声波可以根据不同的特性进行分类&#xff0c;主要包括频率和传播方式两个方面&#xff1a; ### 按频率分类&#xff1a; 1. **次声波**&#xff1a;频率低于20Hz的机械波&#xff0c;这类波通常不能被人耳感知。 2. **可闻声波**&#xff1a;频率在20Hz至20kHz之间的机械波&am…

C++ | Leetcode C++题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {if (headA nullptr || headB nullptr) {return nullptr;}ListNode *pA headA, *pB headB;while (pA ! pB) {pA pA nullptr ? headB : p…

【fiddler】fiddler抓取websocket

1.先了解websocket流 下载4.5版本以上的fiddler 如图所示&#xff1a;在rules--customize rules 里面插入以下代码&#xff1a; static function OnWebSocketMessage(oMsg: WebSocketMessage) { // Log Message to the LOG tab FiddlerApplication.Log.LogString(oMsg.ToStr…

鸿蒙开发下拉选项框在表单递交的处理

下拉选项框 <select name"identity"><option value"0">顾 客</option><option value"1">行 政</option><option value"2" >保 洁</option></select>在表单数据中没有找到identit…

Win11 Docker Desktop下部署springboot jar

1.将springboot程序使用maven package打包出jar。 2.创建dockerfile&#xff0c;为了本地打包时方便&#xff0c;这里的dockerfile有小变动。 # Docker Desktop下部署springboot jar FROM openjdk:8 VOLUME /tmp EXPOSE 8601 ARG JAR_FILEtarget/webflux-hello-0.0.1-SNAPSHO…

AVL树插入详解

1.什么是AVL树 二叉搜索树可以提高搜索的效率&#xff0c;但是如果数据有序或者接近有序&#xff0c;就会退化为单边树&#xff0c;查找效率相当于在顺序表中查找数据&#xff0c;时间复杂度会退化到O(n)。AVL树解决了这个问题&#xff0c;通过保证每个节点的左右子树高度之差…