Spring Boot循环依赖全解析:原理、解决方案与最佳实践
🚨 Spring Boot循环依赖全解析:原理、解决方案与最佳实践
#SpringBoot核心 #依赖注入 #设计模式 #性能优化
一、循环依赖的本质与危害
1.1 什么是循环依赖?
循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系。
典型场景:
@Service
public class ServiceA { @Autowired private ServiceB serviceB;
} @Service
public class ServiceB { @Autowired private ServiceA serviceA;
}
Spring启动时抛出异常:
BeanCurrentlyInCreationException: Error creating bean with name 'serviceA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
1.2 核心危害
- 应用启动失败:Spring无法完成Bean初始化
- 设计缺陷信号:模块职责不清,耦合度过高
- 潜在性能问题:即使解决循环依赖,可能引发隐藏的初始化顺序问题
二、Spring的三级缓存机制
Spring通过三级缓存解决Setter/Field注入的循环依赖,但无法解决构造器注入的循环依赖。
2.1 三级缓存结构
| 缓存级别 | 存储内容 |
|---|---|
| 一级缓存(singletonObjects) | 完全初始化的Bean |
| 二级缓存(earlySingletonObjects) | 提前暴露的早期Bean(未完成属性填充) |
| 三级缓存(singletonFactories) | Bean工厂对象(用于生成早期引用) |
2.2 解决流程(以ServiceA和ServiceB为例)
1. 创建ServiceA → 将原始对象工厂放入三级缓存
2. 填充ServiceA属性 → 发现需要ServiceB
3. 创建ServiceB → 将原始对象工厂放入三级缓存
4. 填充ServiceB属性 → 从三级缓存获取ServiceA的工厂 → 生成代理对象
5. ServiceB初始化完成 → 移入一级缓存
6. ServiceA继续填充ServiceB → 从一级缓存获取ServiceB → 完成初始化
三、解决方案与代码实战
3.1 避免构造器注入循环
构造器注入循环依赖无法解决(Spring 5.3+默认禁止):
// 错误示例:启动直接失败
@Service
public class ServiceA { private final ServiceB serviceB; public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }
} @Service
public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; }
}
强制允许构造器循环依赖(不推荐):
# application.properties
spring.main.allow-circular-references=true
3.2 使用Setter/Field注入
将构造器注入改为Setter注入:
@Service
public class ServiceA { private ServiceB serviceB; @Autowired public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
} @Service
public class ServiceB { private ServiceA serviceA; @Autowired public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; }
}
3.3 @Lazy延迟加载
强制延迟其中一个Bean的初始化:
@Service
public class ServiceA { @Lazy @Autowired private ServiceB serviceB;
}
原理:ServiceB被代理,首次调用时才会真实初始化。
3.4 接口抽象解耦
通过接口隔离实现类依赖:
public interface IServiceA { void doSomething();
} public interface IServiceB { void doAnother();
} @Service
public class ServiceAImpl implements IServiceA { @Autowired private IServiceB serviceB;
} @Service
public class ServiceBImpl implements IServiceB { @Autowired private IServiceA serviceA;
}
四、深度优化:设计模式应用
4.1 依赖倒置原则(DIP)
高层模块不应依赖低层模块,二者都应依赖抽象:
// 定义数据访问接口
public interface UserRepository { User findById(Long id);
} // 高层服务依赖接口
@Service
public class UserService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; }
} // 低层实现
@Repository
public class JpaUserRepository implements UserRepository { // 实现细节
}
4.2 事件驱动模型
通过ApplicationEvent解耦强依赖:
// ServiceA发布事件
@Service
public class ServiceA { @Autowired private ApplicationEventPublisher publisher; public void triggerEvent() { publisher.publishEvent(new CustomEvent(this)); }
} // ServiceB监听事件
@Service
public class ServiceB { @EventListener public void handleEvent(CustomEvent event) { // 处理逻辑 }
}
五、检测与预防工具
5.1 IDE检测
- IntelliJ IDEA:自动标记循环依赖(需安装
Spring Assistant插件) - Eclipse:通过
STS (Spring Tool Suite)插件提示
5.2 Maven插件分析
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration>
</plugin>
运行命令:
mvn spring-boot:run -Dspring-boot.run.profiles=dev
5.3 架构规范
- 模块化设计:按业务拆分模块(如
user-service,order-service) - 依赖规则:
- 下层模块可依赖上层
- 同层禁止相互依赖
- 通用工具类下沉至
common模块
六、常见问题解答
Q1:允许循环依赖对性能有影响吗?
- 短期影响:增加Bean创建时的上下文切换
- 长期风险:可能导致内存泄漏(如未正确释放代理对象)
Q2:@Lazy注解可以随便用吗?
- 慎用场景:频繁调用的Bean会增加代理开销
- 最佳实践:仅用于解决无法重构的历史代码
Q3:Spring为什么能解决Setter注入循环依赖?
- 核心机制:三级缓存提前暴露未完成初始化的对象引用
七、总结与最佳实践
黄金法则:
- 优先使用构造器注入:强制暴露依赖关系
- 遵守单一职责原则:拆分超过500行代码的类
- 定期依赖审查:使用ArchUnit等工具检测架构规范
紧急修复流程:
发现循环依赖 → 使用@Lazy临时解决 → 标记为技术债务 → 排期重构
工具推荐:
- ArchUnit:架构规则检测
- Spring Boot Actuator:运行时依赖分析
通过合理设计+规范约束,可有效避免循环依赖,构建高可维护的Spring Boot应用! 🚀
相关文章:
Spring Boot循环依赖全解析:原理、解决方案与最佳实践
🚨 Spring Boot循环依赖全解析:原理、解决方案与最佳实践 #SpringBoot核心 #依赖注入 #设计模式 #性能优化 一、循环依赖的本质与危害 1.1 什么是循环依赖? 循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系。 典…...
力扣 283 移动零的两种高效解法详解
目录 方法一:两次遍历法 方法二:单次遍历交换法 两种方法对比 在解决数组中的零移动到末尾的问题时,我们需要保持非零元素的顺序,并原地修改数组。以下是两种高效的解法及其详细分析。 方法一:两次遍历法 思路分析…...
「2025AIGC终极形态」AI系统源码:文本→图像→音乐→视频生成
—从技术痛点到企业级部署,手把手实现全流程AI内容工厂 行业核心痛点:为什么需要多模态AIGC系统? 1. 工具割裂,效率低下 传统流程: 文案(ChatGPT)→ 配图(Midjourney)→…...
使用CS Roofline Toolkit测量带宽
使用CS Roofline Toolkit测量带宽 工程下载:使用CS Roofline Toolkit测量带宽-案例工程文件,也可以按照下面的说明使用git clone下载 目录 使用CS Roofline Toolkit测量带宽0、Roofline模型理解1、CS Roofline Toolkit下载1.1、设置代理1.2、git clone下…...
L1-4 拯救外星人
题目 你的外星人朋友不认得地球上的加减乘除符号,但是会算阶乘 —— 正整数 N 的阶乘记为 “N!”,是从 1 到 N 的连乘积。所以当他不知道“57”等于多少时,如果你告诉他等于“12!”,他就写出了“479001600”这个答案。 本题就请你…...
现代c++获取linux系统名称
现代c获取linux系统名称 前言一、使用命令获取操作系统名称二、使用c代码获取操作系统名称三、验证四、总结 前言 本文介绍一种使用c获取当前操作系统名称的方法 一、使用命令获取操作系统名称 在linux系统中可以使用uname或者uname -s命令来获取当前操作系统名称,…...
力扣刷题HOT100——53.最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 示例 1: 输入:nums [-2,1,-3,4,-1,2,1,-5,4] 输出:6…...
Java面试黄金宝典48
1. C++ 的拷贝构造函数,深拷贝和浅拷贝 定义 拷贝构造函数:在 C++ 里,拷贝构造函数属于特殊的构造函数,其功能是使用一个已存在的对象来初始化一个新对象。当对象以值传递的方式作为参数传给函数、函数返回对象、用一个对象初始化另一个对象时,拷贝构造函数会被调用。浅拷…...
QuickAPI 核心能力解析:构建数据服务化的三位一体生态
在企业数据资产化运营的进程中,如何打破数据开发与共享的效率瓶颈,实现从 “数据可用” 到 “数据好用” 的跨越?麦聪软件的 QuickAPI 给出了系统性答案。作为 SQL2API 理念的标杆产品,QuickAPI 通过SQL 编辑器、数据 API、数据市…...
ES和MySQL概念对比
基本概念 ES和MySQL都属于数据库,不过各有各的特性,大致使用方法与MySQL类似并无区别。 MySQL:擅长事务持有ACID的特性,确保数据的一致性和安全。 ES:持有倒排索引,适合海量数据搜索和分析。 ES和MySQL如何…...
Spring如何解决项目中的循环依赖问题?
目录 什么是循环依赖? 如何解决? 采用两级缓存解决 需要AOP的Bean的循环依赖问题? 三级缓存解决 什么是循环依赖? 循环依赖就是Spring在初始化Bean时两个不同的Bean你依赖我,我依赖你的情况 例如A依赖B…...
计算机系统---烤机(性能测评)
计算机烤机 一、烤机的定义与核心目的 烤机(Burn-in Test) 是通过对计算机硬件施加持续高负载,模拟极端运行环境,以验证硬件稳定性、性能极限、散热能力及潜在缺陷的测试方法。核心目标包括: 硬件稳定性验证&#x…...
Android开发过程中遇到的SELINUX权限问题
1、selinux权限一般问题 问题详情 log输出如下所示: 01-01 00:00:12.210 1 1 I auditd : type1107 audit(0.0:33): uid0 auid4294967295 ses4294967295 subju:r:init:s0 msg‘avc: denied{ set } for propertypersist.sys.locale pid476 uid1000 gid1000 scontext…...
[Java实战经验]链式编程与Builder模式
目录 链式编程Builder模式 链式编程 链式编程(Fluent AP)是一种编程风格,它通过在同一个对象上连续调用多个方法来执行一系列操作(让方法返回对象本身(return this))。这种风格的编程使代码更加…...
Windows系统docker desktop安装(学习记录)
目前在学习docker,在网上扒了很多老师的教程,终于装好了,于是决定再装一遍做个记录,省的以后再这么麻烦 一:什么是docker Docker 是一个开源的应用容器引擎,它可以让开发者打包他们的应用以及依赖包到一个…...
MIP-Splatting:全流程配置与自制数据集测试【ubuntu20.04】【2025最新版】
一、引言 在计算机视觉和神经渲染领域,3D场景重建与渲染一直是热门研究方向。近期,3D高斯散射(3D Gaussian Splatting)因其高效的渲染速度和优秀的视觉质量而受到广泛关注。然而,当处理大型复杂场景时,这种…...
怎样完成本地模型知识库检索问答RAG
怎样完成本地模型知识库检索问答RAG 目录 怎样完成本地模型知识库检索问答RAG使用密集检索器和系数检索器混合方式完成知识库相似检索1. 导入必要的库2. 加载文档3. 文本分割4. 初始化嵌入模型5. 创建向量数据库6. 初始化大语言模型7. 构建问答链8. 提出问题并检索相关文档9. 合…...
XCTF-web(三)
xff_referer 拦截数据包添加:X-Forwarded-For: 123.123.123.123 添加:Referer: https://www.google.com baby_web 提示:想想初始页面是哪个 查看/index.php simple_js 尝试万能密码,没有成功,在源码中找到如下…...
使用Python+xml+shutil修改目标检测图片和对应xml标注文件
使用Pythonxmlshutil修改目标检测图片文件名和对应xml标注文件: import os import glob import xml.etree.ElementTree as et import shutildef change_labels(source_dir):name_id 18001file_list glob.glob(os.path.join(source_dir, "*.xml"))print…...
How AI could empower any business - Andrew Ng
How AI could empower any business - Andrew Ng References 人工智能如何为任何业务提供支持 empower /ɪmˈpaʊə(r)/ vt. 授权;给 (某人) ...的权力;使控制局势;增加 (某人的) 自主权When I think about the rise of AI, I’m reminded …...
地理人工智能中位置编码的综述:方法与应用
以下是对论文 《A Review of Location Encoding for GeoAI: Methods and Applications》 的大纲和摘要整理: A Review of Location Encoding for GeoAI: Methods and Applications 摘要(Summary) 本文系统综述了地理人工智能(G…...
Verilog的整数除法
1、可变系数除法实现----利用除法的本质 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2025/04/15 13:45:39 // Design Name: // Module Name: divide_1 // Project Name: // Target Devices: // Tool Versions: // Description: // // Depe…...
C++ | STL之list详解:双向链表的灵活操作与高效实践
引言 std::list 是C STL中基于双向链表实现的顺序容器,擅长高效插入和删除操作,尤其适用于频繁修改中间元素的场景。与std::vector不同,std::list的内存非连续,但提供了稳定的迭代器和灵活的元素管理。本文将全面解析std::list的…...
React 把一系列 state 更新加入队列
把一系列 state 更新加入队列 设置组件 state 会把一次重新渲染加入队列。但有时你可能会希望在下次渲染加入队列之前对 state 的值执行多次操作。为此,了解 React 如何批量更新 state 会很有帮助。 开发环境:Reacttsantd 学习内容 什么是“批处理”以…...
【大模型理论篇】Search-R1: 通过强化学习训练LLM推理与利⽤搜索引擎
最近基于强化学习框架来实现大模型在推理和检索能力增强的项目很多,也是Deep Research技术持续演进的缩影。之前我们讨论过《R1-Searcher:通过强化学习激励llm的搜索能⼒》,今天我们分析下Search-R1【1】。 1. 研究背景与问题 ⼤模型(LLM&a…...
Google政策大更新:影响金融,新闻,社交等所有类别App
Google Play 4月10日 迎来了2025年第一次大版本更新,新政主要涉及金融(个人贷款),新闻两个行业。但澄清内容部分却使得所有行业都需进行一定的更新。下面,我们依次从金融(个人贷款),…...
什么时候触发full GC(发生场景)
文章目录 1. 老年代空间不足2. 分配担保失败3. 显式调用`System.gc()`4. 元空间/永久代空间不足5. CMS/G1的并发失败6. 空间分配担保机制7. 堆内存碎片化8. 其他场景总结回答在Java中,Full GC(全局垃圾回收)会回收整个堆内存(包括年轻代、老年代)以及元空间(或永久代)。…...
NO.93十六届蓝桥杯备战|图论基础-拓扑排序|有向无环图|AOV网|摄像头|最大食物链计数|杂物(C++)
有向⽆环图 若⼀个有向图中不存在回路,则称为有向⽆环图(directed acycline graph),简称 DAG 图 AOV⽹ 举⼀个现实中的例⼦:课程的学习是有优先次序的,如果规划不当会严重影响学习效果。课程间的先后次序可以⽤有向图表⽰ 在…...
每日文献(十三)——Part one
今天看的是《RefineNet: Iterative Refinement for Accurate Object Localization》。 目录 零、摘要 0.1 原文 0.2 译文 一、介绍 二、RefineNet A. Fast R-CNN B. Faster R-CNN C. RefineNet 训练 D. RefineNet 测试 零、摘要 0.1 原文 We investigate a new str…...
游戏引擎学习第225天
只能说太难了 回顾当前的进度 我们正在进行一个完整游戏的开发,并在直播中同步推进。上周我们刚刚完成了过场动画系统的初步实现,把开场动画基本拼接完成,整体效果非常流畅。看到动画顺利呈现,令人十分满意,整个系统…...
