Spring 如何解决循环依赖问题 - 三级缓存

1. 什么是循环依赖问题 ?

        循环依赖问题是指对象与对象之间存在相互依赖关系,而且形成了一个闭环,导致两个或多个对象都无法准确的完成对象的创建和初始化。

两个对象间的循环依赖:

多个对象间的循环依赖 :

解决 Spring 中的循环依赖有两个前提条件:

  1. 存在相互依赖的 Bean 必须是单例的 Bean;
  2. 依赖注入 Bean 的方式不能是构造方法注入。

为什么要满足条件 1:

        如果说相互依赖的 Bean 不是单例的,那么 A 需要 B,就得重新 new 一个  B,B 需要 A,就得重新 new 一个 A,新 new 出来的 A 又需要 B,新 new 出来的 B 又需要 A,又进入最初的步骤了,这样下去将会无穷尽也,就根本解决不了了,所以要求存在相互依赖的 Bean 必须是单例的 Bean。

为什么要满足条件 2:

在了解这个原因之前,必须得先了解 Bean  的生命周期,不太清楚的可以先去看看这篇博客:https://blog.csdn.net/xaiobit_hl/article/details/128025295

        因为构造方法的执行时机太靠前了,如果依赖注入 Bean 的方式是构造方法注入,就会导致 A 需要 B 的时候,B 压根还没执行到属性赋值那一步,而 B 需要 A 的时候,A 也压根没执行到属性赋值这一步,这就是先有鸡还是先有蛋的问题了。(A 依赖 B 对象的时候,需要 DI 注入 B 对象到当前对象中,这一步在 Bean 的生命周期中也叫做属性赋值,而需要给 B 进行赋值,B 对象就得先执行完实例化、属性赋值、初始化这三个生命周期)

2. Spring 三级缓存如何解决循环依赖问题 ?

就拿两个对象间的循环依赖来举例:

首先单例对象一定需要存储下来,所以需要一个一级缓存来存储完全初始化好的对象:

A 和  B 相互依赖时, Bean 的执行流程:

A 和 B 相互依赖的执行流程(不牵扯 AOP):

  1. 实例化 A 对象;
  2. 对 A 的依赖对象 B 进行属性赋值,发现 B 对象还没有初始化好,就需要先初始化 B 对象;
  3. B  对象实例化;
  4. 对 B 的依赖对象 A 进行属性赋值,此时 A 还是一个半成品,还没有初始化好,于是 A 对象的引用地址赋值给 B 中的依赖对象;
  5. B 对象进行初始化,B 对象初始化完之后,A 对象也就属性赋值完毕;
  6. A 对象执行初始化了。

        上述流程中,第 4  步,B 对象在给 A 对象进行属性赋值的时候,此时的 A 对象还是个半成品,那么 B 在给 A 属性进行属性赋值的时候,这个半成品也是需要进行存起来的,否则 B 对象后续的流程就无法执行下去了,这时候就有了二级缓存,二级缓存就是用来存放没有初始化好的对象。

        本来有了一级缓存,二级缓存就可以解决循环依赖问题了,但是第三者 AOP 的出现,就导致故事变得复杂起来了。

【前置铺垫】程序中如果使用了 AOP(动态代理),那么 Spring 中存储的就是代理对象,而不是目标对象了,那么在构建代理对象的时候,一定是需要目标对象的,所以代理对象既不能存储在一级缓存,也不能存储在二级缓存,但是这个代理对象是一定要存储下来的,否则就变成多例的了,这就违背了解决循环依赖的前置条件 1,所以这时候就引入了三级缓存。(代理对象中存放的是工厂对象 FactoryBean,代理对象就是通过工厂对象生成的)

这三个缓存在源码中其实就是三个 map :

  1. singletonObjects:一级缓存(ConcurrentHashMap)
  2. singletonFactories:三级缓存(HashMap)
  3. earlySingletonObjects:二级缓存(ConcurrentHashMap)

为什么三级缓存使用 HashMap:(doCreateBean())

因为它里面放的是一个 lambda 表达式,它不是一个真正的对象,所以它就不怕线程安全问题。

所以 Spring 里面解决循环依赖的问题,就是引入了三级缓存来解决的。

程序中如果使用到了 AOP,那么前面 A 依赖 B,B 依赖 A 的执行流程就需要稍作改变:

         

        上述流程执行完毕后,一级缓存中就有了 A 对象和 B 对象了,其他对象需要依赖这俩对象时,就可以从一级缓存中去取了。 

上述流程 4 中,B 进行属性赋值时,寻找 A 对象的流程源码如下:

B 对象执行属性赋值时,寻找 A 对象流程:

        先从一级缓存中找,没找到,就去二级缓存找,也没找到,就去三级缓存找,此时就会执行 A 的 lambda 表达式,此时 A 对象不管是代理对象还是目标对象,都会被晋级到二级缓存中(考虑性能),当其他对象中依赖 A 对象时,就不再需要从三级缓存中拿了,而是直接从二级缓存中取;

        虽然循环依赖问题确实存在,也不可避免,但是 SpringBoot 3.0 以及 Spring framework 6.0 之后,默认情况下就关闭了对于循环依赖的一个支持了,也就是说在 Spring 高版本底下,如果存在循环依赖的问题,Spring 就会告诉你,你的项目中有循环依赖,项目启动失败。


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

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

相关文章

uniapp 微信小程序仿抖音评论区功能,支持展开收起

最近需要写一个评论区功能,所以打算仿照抖音做一个评论功能,支持展开和收起, 首先我们需要对功能做一个拆解,评论区功能,两个模块,一个是发表评论模块,一个是评论展示区。接下来对这两个模块进行…

【数据结构】C语言队列(详解)

前言: 💥🎈个人主页:​​​​​​Dream_Chaser~ 🎈💥 ✨✨专栏:http://t.csdn.cn/oXkBa ⛳⛳本篇内容:c语言数据结构--C语言实现队列 目录 一.队列概念及结构 1.1队列的概念 1.2队列的结构 二.队列的实现 2.1头文…

django-发送邮件

一、业务场景 业务警告 邮箱验证 密码找回 二、邮件相关协议 1.SMYTP(简答邮件传输协议 25端口) 属于“推送”协议 负责发送 2.IMAP(交互式邮件访问协议,应用层协议,143端口) 用于从本地邮件客户端…

ssh访问远程宿主机的VMWare中NAT模式下的虚拟机

1.虚拟机端配置 1.1设置虚拟机的网络为NAT模式 1.2设置虚拟网络端口映射(NAT) 点击主菜单的编辑-虚拟网络编辑器: 启动如下对话框,选中NAT模式的菜单项,并点击NAT设置: 点击添加,为我们的虚拟机添加一个端口映射。…

下岗吧,Excel

ChatGPT的诞生使Excel公式变得过时。通过使用 ChatGPT 的代码解释器你可以做到: 分析数据创建图表 这就像用自然语言与电子表格交谈一样。我将向大家展示如何使用 ChatGPT 执行此操作并将结果导出为Excel格式: 作为示例,我将分析并创建美国…

AI人员打架识别算法

AI打架识别算法通过yolov8网络模型算法框架,AI打架识别算法识别校园打架斗殴行为,发现立即打架斗殴行为算法会立即抓拍告警推送打架事件信息。目标检测架构分为两种,一种是two-stage,一种是one-stage,区别就在于 two-s…

【锐捷】OSPF 多区域配置

【实验名称】 配置 OSPF 多区域。 【实验目的】 配置 OSPF 多区域,理解 OSPF 层次型网络的特点。 【背景描述】 本实验拓扑图中有 3 台路由器,路由器在区域 0 和区域 1 中,路由器 B 在区域 0 和区域 30, 路由器 C 在区域 30。 【需…

【考研数学】矩阵、向量与线性方程组解的关系梳理与讨论

文章目录 引言一、回顾二、梳理齐次线性方程组非齐次线性方程组 写在最后 引言 两个原因让我想写这篇文章,一是做矩阵题目的时候就发现这三货经常绑在一起,让人想去探寻其中奥秘;另一就是今天学了向量组的秩,让我想起来了之前遗留…

基于jenkins自动化部署PHP环境

实验环境 操作系统 IP地址 主机名 角色 CentOS7.5 192.168.147.141 git git服务器 CentOS7.5 192.168.147.142 Jenkins git客户端 jenkins服务器 CentOS7.5 192.168.147.143 web web服务器 具体环境配置见上一篇! 准备git仓库 [rootgit ~]# su -…

无涯教程-Android - Absolute Layout函数

Absolute Layout 可让您指定其子级的确切位置(x/y坐标),绝对布局的灵活性较差且难以维护。 Absolute Layout - 属性 以下是AbsoluteLayout特有的重要属性- Sr.NoAttribute & 描述1 android:id 这是唯一标识布局的ID。 2 android:layout_x 这指定视图的x坐标…

【Git】(六)子模块跟随主仓库切换分支

场景 主仓库:TestGit 子模块:SubModule 分支v1.0 .gitmodules文件 [submodule "Library/SubModule"]path Library/SubModuleurl gitgitee.com:sunriver2000/SubModule.gitbranch 1.0.0.0 分支v2.0 .gitmodules文件 [submodule "Li…

工服穿戴检测算法 工装穿戴识别算法

工服穿戴检测算法 工装穿戴识别算法利用yolo网络模型图像识别技术,工服穿戴检测算法 工装穿戴识别算法可以准确地识别现场人员是否穿戴了正确的工装,包括工作服、安全帽等。一旦检测到未穿戴的情况,将立即发出警报并提示相关人员进行整改。Yo…

W5100S-EVB-PICO主动PING主机IP检测连通性(十)

前言 上一章节我们用我们开发板在UDP组播模式下进行数据回环测试,本章我们用开发板去主动ping主机IP地址来检测与该主机之间网络的连通性。 什么是PING? PING是一种命令, 是用来探测主机到主机之间是否可通信,如果不能ping到某台…

自然语言处理(二):近似训练

近似训练 近似训练(Approximate Training)是指在机器学习中使用近似的方法来训练模型,以降低计算复杂度或提高训练效率。这种方法通常用于处理大规模数据集或复杂模型,其中精确的训练算法可能过于耗时或计算资源不足。 近似训练…

java安全问题处理

一、客户端的计算不可信 1、服务端计算价格,如果不这么做的话,很可能会被黑客利用,商品总价被恶意修改为比较低的价格。 二、客户端提交的参数需要校验 1、误以为客户端的数据来源是服务端,客户端就不可能提交异常数据 2、对参数进…

无涯教程-Android - Frame Layout函数

Frame Layout 旨在遮挡屏幕上的某个区域以显示单个项目,通常,应使用FrameLayout来保存单个子视图,因为在子视图彼此不重叠的情况下,难以以可扩展到不同屏幕尺寸的方式组织子视图。 不过,您可以使用android:layout_grav…

TSMaster小功能分享—Python小程序如何导入外部库

今天给大家介绍TSMaster功能之Python小程序如何导入外部库。通过在 TSMaster 默认的解析器路径下导入外部库来介绍,以便我们去使用 Python 外部库。TSMaster 默认 Python 解析器下安装外部库。 步骤一 在 TSMaster 工具->系统信息->python 环境设置中选择打开…

未来科技城携手加速科技 共建集成电路测试公共服务平台!

8月26日,2023未来产业发展大会在杭州未来科技城国际会议中心开幕!会上,发布了未来科技城培育发展未来产业行动计划,启动了未来产业发展共同体,进行了未来产业公共服务平台签约仪式。未来科技城与加速科技签约共建集成电…

创建一个空的vue项目,配置及步骤

查看需要的环境及插件版本 创建vue命令 默认配置 手动配置 其他 hash和history的区别: hash 模式,url后,会带着#,改变hash,页面不会刷新,不会更改整个页面,只会更改#后面路由配置的内容&#x…

JVM类加载机制

自己编写的Java代码,是如何在各种各样的操作系统上运行起来的? Java文件通过javac编译成class文件,这种中间码被称为字节码,然后由jvm加载字节码,运行时解释器将字节码解释为一行行机器码来执行,在程序运行…