Spring 是如何解决循环依赖问题
Spring 框架通过 三级缓存 机制来解决循环依赖问题。循环依赖是指两个或多个 Bean 相互依赖,形成一个闭环,例如 Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。Spring 通过提前暴露未完全初始化的 Bean 来解决这个问题。
以下是 Spring 解决循环依赖的详细机制和流程:
1. Spring Bean 的生命周期
在理解循环依赖之前,需要了解 Spring Bean 的生命周期。Spring Bean 的创建过程主要包括以下步骤:
- 实例化:通过构造函数或工厂方法创建 Bean 的实例。
- 属性填充:将依赖的 Bean 注入到当前 Bean 中(通过
@Autowired、@Resource等注解或 XML 配置)。 - 初始化:调用初始化方法(如
@PostConstruct、InitializingBean的afterPropertiesSet方法)。 - 销毁:在容器关闭时调用销毁方法。
循环依赖通常发生在 属性填充 阶段。
2. 三级缓存机制
Spring 通过三级缓存来解决循环依赖问题。三级缓存分别是:
- 一级缓存(Singleton Objects):存储完全初始化好的单例 Bean。
- 二级缓存(Early Singleton Objects):存储提前暴露的未完全初始化的 Bean(仅实例化,未填充属性)。
- 三级缓存(Singleton Factories):存储 Bean 的工厂对象(
ObjectFactory),用于生成提前暴露的 Bean。
3. 解决循环依赖的流程
以下是一个典型的循环依赖场景:
- Bean A 依赖 Bean B。
- Bean B 依赖 Bean A。
Spring 解决循环依赖的流程如下:
步骤 1:创建 Bean A
- Spring 开始创建 Bean A,调用构造函数实例化 Bean A。
- 将 Bean A 的工厂对象(
ObjectFactory)放入三级缓存。 - 开始填充 Bean A 的属性,发现 Bean A 依赖 Bean B。
步骤 2:创建 Bean B
- Spring 开始创建 Bean B,调用构造函数实例化 Bean B。
- 将 Bean B 的工厂对象(
ObjectFactory)放入三级缓存。 - 开始填充 Bean B 的属性,发现 Bean B 依赖 Bean A。
步骤 3:解决 Bean B 的依赖
- Spring 从三级缓存中获取 Bean A 的工厂对象,生成 Bean A 的早期引用(未完全初始化的 Bean A)。
- 将 Bean A 的早期引用放入二级缓存,并从三级缓存中移除 Bean A 的工厂对象。
- 将 Bean A 的早期引用注入到 Bean B 中。
- Bean B 完成属性填充和初始化,成为一个完全初始化的 Bean。
- 将 Bean B 放入一级缓存。
步骤 4:完成 Bean A 的创建
- Spring 将 Bean B 注入到 Bean A 中。
- Bean A 完成属性填充和初始化,成为一个完全初始化的 Bean。
- 将 Bean A 放入一级缓存,并从二级缓存中移除 Bean A 的早期引用。
4. 代码示例
以下是一个简单的循环依赖示例:
@Component
public class BeanA {@Autowiredprivate BeanB beanB;
}@Component
public class BeanB {@Autowiredprivate BeanA beanA;
}
Spring 会通过三级缓存机制解决 BeanA 和 BeanB 之间的循环依赖。
5. 解决循环依赖的限制
Spring 的循环依赖解决方案有以下限制:
-
仅支持单例 Bean:Spring 只能解决单例作用域(Singleton)的 Bean 的循环依赖。原型作用域(Prototype)的 Bean 无法解决循环依赖。
-
构造函数注入无法解决循环依赖:如果循环依赖是通过构造函数注入的,Spring 无法解决。因为构造函数注入需要在实例化时完成依赖注入,而三级缓存机制无法提前暴露未完全初始化的 Bean。
@Component public class BeanA {private final BeanB beanB;@Autowiredpublic BeanA(BeanB beanB) {this.beanB = beanB;} }@Component public class BeanB {private final BeanA beanA;@Autowiredpublic BeanB(BeanA beanA) {this.beanA = beanA;} }上述代码会抛出
BeanCurrentlyInCreationException异常。 -
避免复杂的循环依赖:虽然 Spring 可以解决简单的循环依赖,但复杂的循环依赖可能会导致代码难以维护和理解,应尽量避免。
6. 如何避免循环依赖
- 使用 Setter 注入或字段注入:避免使用构造函数注入。
- 重新设计代码:通过重新设计类之间的关系,消除循环依赖。
- 使用
@Lazy注解:延迟加载依赖的 Bean。@Component public class BeanA {private final BeanB beanB;@Autowiredpublic BeanA(@Lazy BeanB beanB) {this.beanB = beanB;} }
7. 缓存状态的变化
以下是缓存状态的变化过程:
| 步骤 | 一级缓存 (singletonObjects) | 二级缓存 (earlySingletonObjects) | 三级缓存 (singletonFactories) |
|---|---|---|---|
创建 BeanA 实例后 | 无 | 无 | BeanA 的工厂对象 |
创建 BeanB 实例后 | 无 | 无 | BeanA 和 BeanB 的工厂对象 |
解决 BeanB 依赖后 | 无 | BeanA 的早期引用 | BeanB 的工厂对象 |
BeanB 创建完成后 | BeanB | BeanA 的早期引用 | 无 |
BeanA 创建完成后 | BeanA 和 BeanB | 无 | 无 |
总结
Spring 通过三级缓存机制解决了单例 Bean 的循环依赖问题,但构造函数注入和原型 Bean 的循环依赖无法解决。在实际开发中,应尽量避免循环依赖,保持代码的清晰和可维护性。如果无法避免,可以使用 Setter 注入或 @Lazy 注解来解决。
相关文章:
Spring 是如何解决循环依赖问题
Spring 框架通过 三级缓存 机制来解决循环依赖问题。循环依赖是指两个或多个 Bean 相互依赖,形成一个闭环,例如 Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。Spring 通过提前暴露未完全初始化的 Bean 来解决这个问题。 以下是 Spring 解决…...
Linux 目录操作详解
Linux目录操作详解 1. 获取当前工作目录1.1 getcwd()1.2 get_current_dir_name() 2. 切换工作目录2.1 chdir() 3. 创建和删除目录3.1 mkdir()3.2 rmdir() 4. 获取目录中的文件列表4.1 opendir() 打开目录4.2 readdir() 读取目录内容4.3 closedir() 关闭目录 5. dirent 结构体6.…...
Elasticsearch的经典面试题及详细解答
以下是一些Elasticsearch的经典面试题及详细解答: 一、基础概念与原理 什么是Elasticsearch? 回答: Elasticsearch是一个基于Lucene的分布式搜索引擎,提供了RESTful API,支持多租户能力。它能够快速、近实时地存储、搜…...
Linux-arm(1)ATF启动流程
Linux-arm(1)ATF启动流量 Author:Once Day Date:2025年1月22日 漫漫长路有人对你微笑过嘛… 全系列文章可查看专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文档: ARM Trusted Firmware分析——启动、PSCI、OP-TEE接口 Arnold Lu 博…...
C#编程:List.ForEach与foreach循环的深度对比
在C#中,List<T>.ForEach 方法和传统的 foreach 循环都用于遍历列表中的元素并对每个元素执行操作,但它们之间有一些关键的区别。 List<T>.ForEach 方法 方法签名:public void ForEach(Action<T> action)类型:…...
C语言文件操作:标准库与系统调用实践
目录 1、C语言标准库文件操作 1.1.题目要求: 1.2.函数讲解: fopen 函数原型 参数 常用的打开模式 返回值 fwrite函数 函数原型 参数 返回值 注意事项 fseek函数 函数原型 参数 返回值 fread函数 函数原型 参数 返回值 fclose 函数…...
代码随想录 栈与队列 test 7
347. 前 K 个高频元素 - 力扣(LeetCode) 首先想到哈希,用key来存元素,value来存出现次数,最后进行排序,时间复杂度约为o(nlogn)。由于只需求前k个,因此可以进行优化,利用堆来维护这…...
C语言练习(21)
有一行电文,已按下面规律译成密码: A→Za→Z B→Yb→y C→Xc→X 即第1个字母变成第26个字母,第2个字母变成第25个字母,第i个字母变成第(26-i十1)个字母。非字母字符不变。假如已知道密码是Umtorhs&…...
智能手机“混战”2025:谁将倒下而谁又将突围?
【潮汐商业评论原创】 “去年做手机比较艰难,几乎每个品牌都在调价、压货,像华为这种以前都不给我们分货的厂商,也开始成为我的主要库存。不过今年开头比较好,20号国补一开始,店里的人流和手机销量就明显涨了不少&…...
计算机图形学:实验一 OpenGL基本绘制
1.OpenGL的环境配置: 集成开发环境Visual Studio Community 2019的安装: 在Windows一栏选择使用C的桌面开发;再转到“单个组件”界面,在“编译器、生成工具和运行时”一栏选择用于“Windows的C CMake工具”;然后转到…...
二分查找题目:快照数组
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题:快照数组 出处:1146. 快照数组 难度 7 级 题目描述 要求 实现支持下列接口的快照数组: SnapshotArray(int length) \textt…...
深度学习|表示学习|卷积神经网络|参数共享是什么?|07
如是我闻: Parameter Sharing(参数共享)是卷积神经网络(CNN)的一个重要特性,帮助它高效地处理数据。参数共享的本质就是参数“本来也没有变过”。换句话说,在卷积层中,卷积核的参数&…...
基于相机内参推导的透视投影矩阵
基于相机内参推导透视投影矩阵(splatam): M c a m [ 2 ⋅ f x w 0.0 ( w − 2 ⋅ c x ) w 0.0 0.0 2 ⋅ f y h ( h − 2 ⋅ c y ) h 0.0 0 0 f a r n e a r n e a r − f a r 2 f a r ⋅ n e a r n e a r − f a r 0.0 0.0 − 1.0 0.0 ] M_…...
浅析Dubbo 原理:架构、通信与调用流程
一、Dubbo 简介 Dubbo 是阿里巴巴开源的高性能、轻量级的 Java RPC(Remote Procedure Call,远程过程调用)框架,旨在实现不同服务之间的远程通信和调用。在分布式系统中,不同服务可能部署在不同的服务器上,D…...
03垃圾回收篇(D3_垃圾收集器的选择及相关参数)
目录 学习前言 一、收集器的选择 二、GC日志参数 三、垃圾收集相关的常用参数 四、内存分配与回收策略 1. 对象优先在Eden分配 2. 大对象直接进入老年代 3. 长期存活的对象将进入老年代 4. 动态对象年龄判定 5. 空间分配担保 学习前言 本章主要学习垃圾收集器的选择及…...
一、引论,《组合数学(第4版)》卢开澄 卢华明
零、前言 发现自己数数题做的很烂,重新学一遍组合数学吧。 参考卢开澄 卢华明 编著的《组合数学(第4版)》,只打算学前四章。 通过几个经典问题来了解组合数学所研究的内容。 一、幻方问题 据说大禹治水之前,河里冒出来一只乌龟,…...
Vue3+TS 实现批量拖拽文件夹上传图片组件封装
1、html 代码: 代码中的表格引入了 vxe-table 插件 <Tag /> 是自己封装的说明组件 表格列表这块我使用了插槽来增加扩展性,可根据自己需求,在组件外部做调整 <template><div class"dragUpload"><el-dialo…...
二叉树的所有路径(力扣257)
因为题目要求路径是从上到下的,所以最好采用前序遍历。这样可以保证按从上到下的顺序将节点的值存入一个路径数组中。另外,此题还有一个难点就是如何求得所有路径。为了解决这个问题,我们需要用到回溯。回溯和递归不分家,每递归一…...
Python OrderedDict 实现 Least Recently used(LRU)缓存
OrderedDict 实现 Least Recently used(LRU)缓存 引言正文 引言 LRU 缓存是一种缓存替换策略,当缓存空间不足时,会移除最久未使用的数据以腾出空间存放新的数据。LRU 缓存的特点: 有限容量:缓存拥有固定的…...
LabVIEW项目中的工控机与普通电脑选择
工控机(Industrial PC)与普通电脑在硬件设计、性能要求、稳定性、环境适应性等方面存在显著差异。了解这些区别对于在LabVIEW项目中选择合适的硬件至关重要。下面将详细分析这两种设备的主要差异,并为LabVIEW项目中的选择提供指导。 硬件设…...
从一次生产事故复盘:我们如何优雅地处理用户上传的‘异常’Excel文件(附Apache POI配置详解)
从生产事故到防御体系:构建Excel文件处理的工程化解决方案那天凌晨2点,我被一阵急促的告警声惊醒。监控系统显示,核心文件处理服务的错误率在10分钟内飙升到35%,大量用户上传的Excel文件无法正常解析。更糟糕的是,部分…...
Blender渲染通道完全指南:如何像电影后期一样,分离出深度、阴影与反射图
Blender渲染通道完全指南:影视级后期制作的深度解析在数字内容创作领域,Blender已经从一个简单的3D建模工具成长为能够处理复杂视觉特效的全流程解决方案。对于追求影视级质量的中高级用户而言,掌握渲染通道技术是提升作品专业度的关键一步。…...
环境光遮蔽(Ambient Occlusion):揭秘那个让虚拟世界“有重量感“的阴影魔法
一、一个让我"开窍"的老木匠故事 我有个朋友是传统家具的修复师,他给我讲过一个让我至今难忘的故事。他说他刚入行时跟着一位 70 多岁的老木匠师父学习——师父让他做的第一件事不是雕花、不是榫卯——而是"看阴影"——这个看似奇怪的训练改变了…...
Wechat2RSS:微信公众号转RSS订阅工具
文章目录Wechat2RSS:微信公众号转RSS订阅工具Wechat2RSS:微信公众号转RSS订阅工具 ttttmr开源的Wechat2RSS项目,目前在GitHub上获得1409颗Star,项目地址为https://github.com/ttttmr/Wechat2RSS。该工具的核心作用是将微信公众号…...
sudo企业级应用【20260525】001篇
文章目录 一、总体设计思路 1️⃣ 设计原则 2️⃣ 日志策略(重点) 二、10 个真实生产场景(含 sudoers 配置) 🔹 Linux 系统管理(3 个) ✅ 场景 1:基础运维(用户 / 权限) ✅ 场景 2:磁盘与文件系统 ✅ 场景 3:网络与防火墙 🔹 云管理(2 个) ✅ 场景 4:云 CLI …...
转行网络安全运维:从0到1的可落地指南
转行网络安全运维:从0到1的可落地指南 一、 「3个核心技能:从零起步也能会」 网上学习资料多到爆炸,不用纠结“哪个最好”,记住一句话:**能学会、能上手的就是好的**!不管是免费视频还是付费课,…...
3分钟解锁网易云音乐NCM文件:ncmdumpGUI小白也能懂的完整教程
3分钟解锁网易云音乐NCM文件:ncmdumpGUI小白也能懂的完整教程 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾经下载了网易云音乐的歌曲&a…...
Qri高级功能:如何使用JSON Schema验证和描述数据集结构
Qri高级功能:如何使用JSON Schema验证和描述数据集结构 【免费下载链接】qri youre invited to a data party! 项目地址: https://gitcode.com/gh_mirrors/qr/qri Qri是一个强大的开源数据协作工具,它提供了丰富的功能来帮助用户管理、共享和验证…...
对比按量计费与Token Plan套餐的实际成本差异
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 对比按量计费与Token Plan套餐的实际成本差异 在构建和运营基于大模型的应用时,成本控制是一个核心的工程考量。Taotok…...
PostgreSQL Merge Join 大白话详解
用生活中最直观的例子,彻底搞懂 Merge Join 是什么、为什么快、什么时候用。一、先从生活场景开始 场景一:两摞乱序试卷找同学 期末考试,老师手里有两摞试卷: A 摞:数学试卷,500 份,乱序堆放B 摞…...
