Spring 源码解读:解决循环依赖的三种方式
引言
在复杂的应用开发中,循环依赖是一个常见的问题。简单来说,循环依赖是指两个或多个Bean之间互相依赖,导致程序无法正常实例化这些Bean。Spring容器通过依赖注入(DI)来管理Bean的创建与生命周期,并在遇到循环依赖时采取了多种策略进行处理。本篇文章将带你实现三种解决循环依赖的方式,包括构造函数注入、Setter注入和ObjectFactory
方式,并对比Spring的循环依赖处理机制,帮助你理解不同的处理方式及其应用场景。
什么是循环依赖
循环依赖(Circular Dependency)是指两个或多个Bean互相依赖,导致它们无法正常实例化。通常情况下,Spring通过依赖注入来管理Bean的生命周期,当遇到循环依赖时,Spring必须采取额外的策略来解决这个问题。
常见的循环依赖类型
-
构造函数循环依赖:
- 两个Bean通过构造函数相互依赖,导致它们在实例化时陷入循环。
-
Setter方法循环依赖:
- 两个Bean通过Setter方法相互依赖,Spring可以通过提前暴露Bean的部分引用来解决这个问题。
-
ObjectFactory
方式:- 通过
ObjectFactory
延迟依赖注入,避免在Bean创建时立即解决依赖,允许Bean先部分创建。
- 通过
手动实现三种循环依赖的解决方式
为了更好地理解循环依赖的处理方式,我们将手动实现三种常见的解决策略:构造函数注入、Setter方法注入和ObjectFactory
方式。
实现构造函数注入的循环依赖
构造函数注入的循环依赖比较难解决,因为它要求所有的依赖在实例化时就已经准备好。下面我们通过一个示例展示构造函数循环依赖的问题。
示例代码
public class ServiceA {private ServiceB serviceB;// 使用构造函数注入ServiceBpublic ServiceA(ServiceB serviceB) {this.serviceB = serviceB; // ServiceA依赖于ServiceB}public void doSomething() {System.out.println("ServiceA is doing something...");}
}public class ServiceB {private ServiceA serviceA;// 使用构造函数注入ServiceApublic ServiceB(ServiceA serviceA) {this.serviceA = serviceA; // ServiceB依赖于ServiceA}public void doSomething() {System.out.println("ServiceB is doing something...");}
}public class ConstructorInjectionTest {public static void main(String[] args) {// 手动实例化构造函数依赖会导致循环依赖问题// ServiceA serviceA = new ServiceA(new ServiceB(serviceA)); // 这会导致无限递归,无法解决}
}
问题描述:
- 在上面的代码中,
ServiceA
通过构造函数依赖ServiceB
,同时ServiceB
也通过构造函数依赖ServiceA
。由于构造函数注入要求在实例化时就提供依赖,因此出现了循环依赖问题,导致递归创建的错误。
解决方法:Setter方法注入
Setter方法注入的循环依赖比构造函数注入要容易解决,因为Spring可以提前暴露部分未完成的Bean引用,在Bean完全实例化前进行部分注入。
示例代码
public class ServiceA {private ServiceB serviceB;// 通过Setter方法注入ServiceBpublic void setServiceB(ServiceB serviceB) {this.serviceB = serviceB; // ServiceA依赖于ServiceB}public void doSomething() {System.out.println("ServiceA is doing something...");}
}public class ServiceB {private ServiceA serviceA;// 通过Setter方法注入ServiceApublic void setServiceA(ServiceA serviceA) {this.serviceA = serviceA; // ServiceB依赖于ServiceA}public void doSomething() {System.out.println("ServiceB is doing something...");}
}public class SetterInjectionTest {public static void main(String[] args) {// 创建实例ServiceA serviceA = new ServiceA();ServiceB serviceB = new ServiceB();// 通过Setter方法解决循环依赖serviceA.setServiceB(serviceB); // ServiceA依赖ServiceB,延迟注入serviceB.setServiceA(serviceA); // ServiceB依赖ServiceA,延迟注入// 测试方法调用serviceA.doSomething(); // 输出:ServiceA is doing something...serviceB.doSomething(); // 输出:ServiceB is doing something...}
}
解决思路:
- 使用Setter方法注入解决循环依赖问题,通过先创建空的Bean对象,再通过Setter方法进行依赖注入。Spring能够在部分Bean完成初始化时将其暴露给其他Bean,从而解决循环依赖问题。
解决方法:ObjectFactory
延迟注入
ObjectFactory
方式通过延迟注入来解决循环依赖问题。ObjectFactory
允许Spring在需要时才创建依赖对象,从而避免在Bean初始化时立即解决依赖。
示例代码
import org.springframework.beans.factory.ObjectFactory;public class ServiceA {private ObjectFactory<ServiceB> serviceBFactory;// 使用ObjectFactory进行延迟注入public ServiceA(ObjectFactory<ServiceB> serviceBFactory) {this.serviceBFactory = serviceBFactory; // 延迟注入ServiceB}public void doSomething() {System.out.println("ServiceA is doing something...");serviceBFactory.getObject().doSomething(); // 当需要时才获取ServiceB}
}public class ServiceB {private ObjectFactory<ServiceA> serviceAFactory;// 使用ObjectFactory进行延迟注入public ServiceB(ObjectFactory<ServiceA> serviceAFactory) {this.serviceAFactory = serviceAFactory; // 延迟注入ServiceA}public void doSomething() {System.out.println("ServiceB is doing something...");serviceAFactory.getObject().doSomething(); // 当需要时才获取ServiceA}
}public class ObjectFactoryInjectionTest {public static void main(String[] args) {// 使用ObjectFactory进行延迟注入,解决循环依赖问题ObjectFactory<ServiceA> serviceAFactory = () -> new ServiceA(() -> new ServiceB(serviceAFactory));ObjectFactory<ServiceB> serviceBFactory = () -> new ServiceB(serviceAFactory);// 创建ServiceA和ServiceB的实例ServiceA serviceA = serviceAFactory.getObject();ServiceB serviceB = serviceBFactory.getObject();// 测试方法调用serviceA.doSomething(); // 输出:ServiceA is doing something... ServiceB is doing something...serviceB.doSomething(); // 输出:ServiceB is doing something... ServiceA is doing something...}
}
解决思路:
ObjectFactory
方式通过延迟创建对象的方式解决循环依赖问题。ObjectFactory
允许在依赖实际使用时才实例化依赖对象,从而打破了Bean初始化时的相互依赖问题。
类图与流程图
为了更好地理解三种解决方式的工作原理,我们提供了类图和流程图。
类图
解释:
ServiceA
和ServiceB
互相依赖,通过ObjectFactory
延迟实例化来避免直接的循环依赖。
流程图
解释:
- 使用
ObjectFactory
延迟注入的方式,ServiceA
和ServiceB
可以在需要时获取对方的实例,避免了直接的循环依赖问题。
Spring中的循环依赖处理机制
在Spring中,循环依赖的处理是通过多种策略实现的,主要包括三级缓存机制。Spring容器通过提前暴露未完成的Bean实例、延迟依赖注入等方式解决循环依赖。
Spring的三级缓存机制
Spring容器内部通过三级缓存来处理循环依赖问题:
1
. 一级缓存:存储完全初始化好的单例Bean。
2. 二级缓存:存储部分实例化、但尚未完成初始化的Bean。
3. 三级缓存:存储可以通过代理对象获取的Bean,用于解决复杂的循环依赖场景。
当Spring遇到循环依赖时,能够通过三级缓存中的代理对象提前暴露未完成的Bean,从而解决依赖问题。
源码解析:Spring如何解决循环依赖
Spring在DefaultSingletonBeanRegistry
类中,通过addSingletonFactory()
方法提前暴露创建中的Bean来解决循环依赖。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory); // 将未完成的Bean放入三级缓存this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}
对比分析:手动实现与Spring的区别
-
Spring的实现:
- Spring采用了三级缓存的机制,通过提前暴露Bean的引用来解决循环依赖。它能够处理复杂的依赖关系和代理对象。
- 三级缓存是Spring容器处理循环依赖的核心策略,通过将未完成的Bean放入三级缓存,可以提前暴露这些Bean的引用。
-
手动实现:
- 我们的手动实现展示了三种常见的循环依赖解决方式,虽然能够处理基本的循环依赖问题,但缺乏Spring的高级功能,如三级缓存和生命周期管理。
总结
通过实现构造函数注入、Setter方法注入和ObjectFactory
延迟注入三种解决循环依赖的方式,你应该对循环依赖的解决策略有了更深入的理解。在Spring框架中,三级缓存机制是其处理循环依赖的核心策略,它能够灵活地解决复杂的依赖关系。理解这些机制,将帮助你在实际开发中更好地管理Bean的生命周期,并在需要时解决循环依赖问题。
互动与思考
你是否在项目中遇到过循环依赖问题?你更倾向于使用哪种解决策略?欢迎在评论区分享你的经验与见解!
如果你觉得这篇文章对你有帮助,请别忘了:
- 点赞 ⭐
- 收藏 📁
- 关注 👀
让我们一起深入学习Spring框架,成为更优秀的开发者!
相关文章:

Spring 源码解读:解决循环依赖的三种方式
引言 在复杂的应用开发中,循环依赖是一个常见的问题。简单来说,循环依赖是指两个或多个Bean之间互相依赖,导致程序无法正常实例化这些Bean。Spring容器通过依赖注入(DI)来管理Bean的创建与生命周期,并在遇…...

Web3 详解
1. 使用 Web3 库 Web3 是一个 JavaScript 库,可用于通过 RPC 通信与以太坊节点通信。 Web3 的工作方式是,公开已通过 RPC 启用的方法,这允许开发利用 Web3 库的用户界面,以便与部署在区块链上的合约进行交互。 一旦 Geth JavaScri…...

Spring 中依赖注入注解的区别详解
一、依赖注入的基本概念 依赖注入是一种设计模式,通过将对象的依赖以参数的形式传入类中,而不是在类中自行创建依赖对象。这样做有几个好处: 降低耦合度:类与类之间的依赖关系变得更清晰,避免了硬编码依赖。提高可测试性:通过依赖注入,可以轻松地进行单元测试,因为可以…...

PTA求一批整数中出现最多的个位数字
作者 徐镜春 单位 浙江大学 给定一批整数,分析每个整数的每一位数字,求出现次数最多的个位数字。例如给定3个整数1234、2345、3456,其中出现最多次数的数字是3和4,均出现了3次。 输入格式: 输入在第1行中给出正整数…...

探索国产编程工具:如何实现工作效率翻倍
在当前软件开发领域,国产编程工具正在迅速发展,它们在功能、性能以及用户体验上都有显著提升,以下是一些国产编程工具,它们可以帮助开发者提升工作效率。 智能代码编辑器 CodeGeeX:这是一款由清华大学和智谱AI合作开…...

秒懂:进程相关的操作
1.进程的查看 1.1创建test.cc文件,运行以下代码 #include <stdio.h> #include <sys/types.h> #include <unistd.h>int main() {while(1){sleep(1);} return 0;}1.2 执行以下命令 1. 运行test.cc文件 并将其最终的可执行文件命名为 test gcc t…...

PDF 软件如何帮助您编辑、转换和保护文件。
如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案,还是尝试组织和编辑主文档,PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时,请考虑这些因素。 1. 确定您的…...

蓝桥杯嵌入式国三备赛经验分享
1 学习STM32入门视频 向大家推荐一套宝藏级别的视频:【STM32入门教程-2023版 细致讲解 中文字幕】 如果已经比过蓝桥杯单片机或学习过单片机相关课程的同学,你们可以尝试不需要STM32套件进行学习。如果没有学过单片机相关课程的同学,可以买…...

AI编程工具合集
1. 简介 1.1. 概述 AI编程,即人工智能编程,是编写用于创建智能系统(如机器学习模型、自然语言处理应用程序等)的代码的过程。AI编程涉及使用算法和数据结构来实现能够执行任务的程序,这些任务通常需要人类智能才能完成。 AI编程的基础是计算机科学原理,包括数据结构、…...

[网络编程]通过java用TCP实现网络编程
文章目录 一. 通过java用TCP实现网络编程api介绍代码实现上述代码存在的问题 一. 通过java用TCP实现网络编程 api介绍 1. ServerSocket ServerSocket是专门给服务器用的api 构造方法: 方法: 2. Socket 不管是客⼾端还是服务端Socket,都是双⽅建⽴连接以后&#…...

Python(TensorFlow)和Java及C++受激发射损耗导图
🎯要点 神经网络监督去噪预测算法聚焦荧光团和检测模拟平台伪影消除算法性能优化方法自动化多尺度囊泡动力学成像生物研究多维分析统计物距粒子概率算法 Python和MATLAB图像降噪算法 消除噪声的一种方法是将原始图像与表示低通滤波器或平滑操作的掩模进行卷积。…...

IEEE投稿模板翻译
>将这一行替换为您的稿件id号(双击此处编辑)< IEEE 期刊和会议论文的撰写准备(2022) 第一作者 A. 作者,IEEE成员,第二作者 B. 作者,第三作者 C. 作者 Jr.,IEEE成员 摘要—本文档为IEEE会刊、期刊和…...

log4j 1.x 日志输出线程以唯一ID的形式配置
在 Log4j 1.x 中,直接以线程ID(如Java中的Thread.currentThread().getId()返回的ID)的形式记录日志是可行的,但 Log4j 1.x 本身并不直接提供一个内建的、自动将每个线程ID转换为“同一时间段内唯一ID”的机制。线程ID本身在JVM的上…...

宏观学习笔记:GDP分析(二)
GDP分析(一)主要是介绍GDP相关的定义以及核算逻辑,本节主要介绍GDP的分析思路。GDP分析主要是2种方法:总量分析和结构分析。 1. 总量分析 1.1 数值选择 一般情况下,分析的对象都是 官方公布的GDP当季值。 1.2 趋势规…...

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容
1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为…...

C高级编程 第十六天(树 二叉树)
1.树 1.1结构特点 非线性结构,有一个直接前驱,但可能有多个直接后继有递归性,树中还有树可以为空,即节点个数为零 1.2相关术语 根:即根结点,没有前驱叶子:即终端结点,没有后继森…...

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆,该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使…...

904.水果成篮
题目 链接:leetcode链接 思路分析(滑动窗口) 读完题目,很明显,这个题目需要我们寻找一个最长子数组,使得这个子数组里面最多存在两种不同的数字,很容易联想到使用滑动窗口。 另外ÿ…...

【网络安全】漏洞挖掘之 2FA 恢复代码安全措施不当
未经许可,不得转载。 文章目录 正文正文 目标:example.com 2024年6月,我在HackerOne上参与一个私人项目时发现了一个与2FA(双因素身份验证)恢复代码管理相关的安全漏洞。该漏洞发生在用户禁用并重新启用2FA的过程中。问题在于,系统在2FA重新启用后,仍然接受此前生成的…...

指令微调与参数微调的代码实践与分析
文章目录 指令微调的实验性分析LoRA 代码实践与分析指令微调的示例代码与预训练的代码高度一致,区别主要在于指令微调数据集的构建(SFTDataset)和序列到序列损失的计算(DataCollatorForSupervisedDataset)。以下代码展示了 LLMBox 和 YuLan-Chat 中指令微调的整体训练流程…...

Android14音频进阶之高通Elite架构指定通道播放(八十四)
简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…...

常见的正则化方法以及L1,L2正则化的简单描述
深度学习中的正则化是通过在模型训练过程中引入某些技术来防止模型过拟合的一种策略。过拟合是指模型在训练数据上表现非常好,但在新的、未见过的数据上表现不佳。正则化通过限制模型的复杂度或对模型参数施加约束,从而提高模型的泛化能力。 常见的正则…...

深入理解 Milvus:新一代向量数据库的基础技术与实战指南
一、什么是 Milvus? Milvus 是一个开源的向量数据库,专门设计用于存储和检索大规模的高维向量数据。无论是图像、视频、音频还是文本,通过将这些数据转换为向量,Milvus 都能通过近似最近邻搜索(Approximate Nearest N…...

Maven教程——从入门到入坑
第1章 为什么要使用Maven 1.1 获取第三方jar包 开发中需要使用到的jar包种类繁多,获取jar包的方式都不尽相同。为了查找一个jar包找遍互联网,身心俱疲。不仅如此,费劲心血找到的jar包里有的时候并没有你需要的那个类,又或者有…...

研究生深度学习入门的十天学习计划------第九天
第9天:深度学习中的迁移学习与模型微调 目标: 理解迁移学习的核心概念,学习如何在实际应用中对预训练模型进行迁移和微调,以应对不同领域的任务。 9.1 什么是迁移学习? 迁移学习(Transfer Learning&#…...

perl的学习记录——仿真regression
1 记录的背景 之前只知道有这个强大语言的存在,但一直侥幸自己应该不会用到它,所以一直没有开始学习。然而人生这么长,怎就确定自己不会用到呢? 这次要搭建一个可以自动跑完所有case并且打印每个case的pass信息到指定的文件中。…...

【Go】go连接clickhouse使用TCP协议
离开你是傻是对是错 是看破是软弱 这结果是爱是恨或者是什么 如果是种解脱 怎么会还有眷恋在我心窝 那么爱你为什么 🎵 黄品源/莫文蔚《那么爱你为什么》 package mainimport ("context""fmt""log""time&q…...

Emlog-Pro访问网站时需要密码验证插件
插件介绍 EmlogPro访问网站密码验证插件,为你的网站添加输入密码访问网站功能,在应用中的场景往往运用在为内部或是个人使用的页面里面,在访问的时候可以提示输入密码,做隐私保护。 下载地址: Emlog-Pro访问网站时需…...

Apache ShardingSphere数据分片弹性伸缩加解密中间件
Apache ShardingSphere Apache ShardingSphere 是一款分布式 SQL 事务和查询引擎,可通过数据分片、弹性伸缩、加密等能力对任意数据库进行增强。 软件背景 ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding…...

Django+Vue家居全屋定制系统的设计与实现
目录 1 项目介绍2 项目截图3 核心代码3.1 需要的环境3.2 Django接口层3.3 实体类3.4 config.ini3.5 启动类3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者&…...