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项目中的选择提供指导。 硬件设…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...