Spring Boot 2.6.0+ 循环依赖问题及解决方案

Spring Boot 2.6.0+ 循环依赖问题及解决方案

目录

  • 背景
  • 解决方案
    • 1. 配置文件开启循环依赖(侵入性最低,临时方案)
    • 2. @Lazy 延迟注入(侵入性低,推荐优先尝试)
    • 3. 手动从容器获取(ApplicationContextAware,侵入性中等)
    • 4. 接口隔离 / 中间层解耦(侵入性高,推荐长期方案)
    • 5. 事件驱动(ApplicationEvent,侵入性中等,适合通知场景)
  • 总结
  • 版本差异补充
    在这里插入图片描述

背景

从 Spring Boot 2.6.0 开始,Spring 团队默认禁止了循环依赖(circular references),这是一个重要的设计变更。主要原因包括:

  1. 设计原则:循环依赖通常暗示着不良的代码设计,违反了单一职责原则
  2. 初始化问题:循环依赖可能导致难以预测的bean初始化顺序和状态
  3. 调试困难:循环依赖使得问题排查变得复杂
  4. 性能考虑:三级缓存机制增加了额外的内存开销

配置变更

spring:main:allow-circular-references: false  # 2.6+ 默认值

常见错误信息

The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  dictionaryServiceImpl defined in file [...DictionaryServiceImpl.class]
↑     ↓
|  dictionaryDataServiceImpl defined in file [...DictionaryDataServiceImpl.class]
└─────┘

解决方案

1. 配置文件开启循环依赖(侵入性最低,临时方案)

适用场景:快速解决遗留项目的启动问题,临时过渡方案

# application.yml
spring:main:allow-circular-references: true

优点

  • 零代码修改
  • 立即生效
  • 保持原有业务逻辑不变

缺点

  • 治标不治本
  • 可能隐藏潜在的设计问题
  • 不符合Spring新版本的设计理念

2. @Lazy 延迟注入(侵入性低,推荐优先尝试)

适用场景:简单的双向依赖,不需要在初始化时立即使用依赖对象

实现方式

@Service
public class DictionaryDataServiceImpl implements DictionaryDataService {@Lazy  // 延迟加载,打破循环依赖@Resourceprivate DictionaryService dictionaryService;public void someMethod() {// 只有在真正调用时才会初始化 dictionaryServiceDictionary dict = dictionaryService.getById(1);}
}@Service
public class DictionaryServiceImpl implements DictionaryService {@Resourceprivate DictionaryDataService dictionaryDataService; // 保持正常注入// 业务逻辑...
}

工作原理

  • @Lazy 注入的是一个代理对象,而非真实的bean
  • 只有在第一次调用方法时,才会触发真实bean的创建
  • 从而避开了启动时的循环依赖检查

优点

  • 代码侵入性小
  • 保持了依赖注入的便利性
  • 符合Spring的设计理念

缺点

  • 首次调用时性能略有损失
  • 需要明确哪个依赖使用@Lazy

3. 手动从容器获取(ApplicationContextAware,侵入性中等)

适用场景:需要更灵活的依赖获取方式,或者依赖关系比较复杂

实现方式一:ApplicationContextAware接口

@Service
public class DictionaryDataServiceImpl implements DictionaryDataService, ApplicationContextAware {private ApplicationContext applicationContext;private DictionaryService dictionaryService;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}private DictionaryService getDictionaryService() {if (dictionaryService == null) {dictionaryService = applicationContext.getBean(DictionaryService.class);}return dictionaryService;}public void someMethod() {Dictionary dict = getDictionaryService().getById(1);}
}

实现方式二:SpringContextHolder工具类

// 工具类
@Component
public class SpringContextHolder implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {context = applicationContext;}public static <T> T getBean(Class<T> beanClass) {return context.getBean(beanClass);}public static <T> T getBean(String beanName, Class<T> beanClass) {return context.getBean(beanName, beanClass);}
}// 业务类使用
@Service
public class DictionaryDataServiceImpl implements DictionaryDataService {public void someMethod() {DictionaryService dictionaryService = SpringContextHolder.getBean(DictionaryService.class);Dictionary dict = dictionaryService.getById(1);}
}

优点

  • 完全避免了循环依赖
  • 可以动态获取bean
  • 适合复杂的依赖场景

缺点

  • 失去了依赖注入的便利性
  • 代码可读性稍差
  • 增加了与Spring框架的耦合

4. 接口隔离 / 中间层解耦(侵入性高,推荐长期方案)

适用场景:重构现有架构,从根本上解决循环依赖问题

方案一:提取公共服务

// 提取公共逻辑到新的服务
@Service
public class DictionaryCommonService {@Resourceprivate DictionaryMapper dictionaryMapper;@Resource private DictionaryDataMapper dictionaryDataMapper;public Dictionary findDictionaryById(Integer id) {return dictionaryMapper.selectById(id);}public List<DictionaryData> findDatasByDictId(Integer dictId) {return dictionaryDataMapper.selectByDictId(dictId);}
}// 重构后的服务
@Service
public class DictionaryServiceImpl implements DictionaryService {@Resourceprivate DictionaryCommonService dictionaryCommonService;@Overridepublic JsonResult articleTypeList() {// 使用公共服务Dictionary dict = dictionaryCommonService.findDictionaryById(1);List<DictionaryData> dataList = dictionaryCommonService.findDatasByDictId(dict.getDictId());// 处理逻辑...}
}@Service
public class DictionaryDataServiceImpl implements DictionaryDataService {@Resourceprivate DictionaryCommonService dictionaryCommonService;// 业务逻辑使用公共服务
}

方案二:接口隔离原则

// 定义最小化接口
public interface DictionaryQueryService {Dictionary getById(Integer id);
}public interface DictionaryDataQueryService {List<DictionaryData> getByDictId(Integer dictId);
}// 实现类只依赖需要的接口
@Service
public class DictionaryServiceImpl implements DictionaryService, DictionaryQueryService {@Resourceprivate DictionaryDataQueryService dictionaryDataQueryService;// 实现逻辑...
}@Service  
public class DictionaryDataServiceImpl implements DictionaryDataService, DictionaryDataQueryService {@Resourceprivate DictionaryQueryService dictionaryQueryService;// 实现逻辑...
}

优点

  • 从根本上解决了设计问题
  • 提高了代码的可维护性
  • 符合SOLID原则
  • 降低了模块间的耦合度

缺点

  • 需要大量的代码重构
  • 可能涉及业务逻辑的调整
  • 短期内工作量较大

5. 事件驱动(ApplicationEvent,侵入性中等,适合通知场景)

适用场景:一个服务需要通知另一个服务执行某些操作

实现方式

// 定义事件
public class DictionaryDataChangeEvent extends ApplicationEvent {private final String dictCode;public DictionaryDataChangeEvent(Object source, String dictCode) {super(source);this.dictCode = dictCode;}public String getDictCode() {return dictCode;}
}// 事件发布者
@Service
public class DictionaryDataServiceImpl implements DictionaryDataService {@Resourceprivate ApplicationEventPublisher eventPublisher;@Overridepublic boolean save(DictionaryData entity) {boolean success = super.save(entity);if (success) {// 发布事件而不是直接调用其他服务eventPublisher.publishEvent(new DictionaryDataChangeEvent(this, entity.getDictCode()));}return success;}
}// 事件监听者
@Service
public class DictionaryServiceImpl implements DictionaryService {@EventListenerpublic void handleDictionaryDataChange(DictionaryDataChangeEvent event) {// 处理字典数据变更后的逻辑String dictCode = event.getDictCode();// 清除缓存、更新状态等}
}

优点

  • 完全解耦了两个服务
  • 支持异步处理
  • 便于扩展(多个监听者)
  • 符合事件驱动架构

缺点

  • 增加了系统复杂度
  • 调试相对困难
  • 需要理解事件驱动模式

总结

方案侵入性适用场景推荐指数备注
配置开启循环依赖最低快速修复、临时方案⭐⭐治标不治本
@Lazy注解简单双向依赖⭐⭐⭐⭐优先推荐
手动获取bean中等复杂依赖关系⭐⭐⭐失去注入便利性
接口隔离/中间层架构重构⭐⭐⭐⭐⭐长期最佳方案
事件驱动中等通知场景⭐⭐⭐⭐解耦效果好

建议处理流程

  1. 短期:使用 @Lazy 快速解决启动问题
  2. 中期:分析业务逻辑,评估是否需要重构
  3. 长期:通过接口隔离或中间层彻底解决循环依赖

版本差异补充

Spring Boot 2.5.x 及之前

  • 默认 allow-circular-references: true
  • 三级缓存自动处理循环依赖
  • 开发者通常不会意识到循环依赖问题

Spring Boot 2.6.0+

  • 默认 allow-circular-references: false
  • 启动时检查并报错
  • 强制开发者关注和解决循环依赖

Spring Boot 3.0+

  • 继续保持对循环依赖的严格控制
  • 进一步鼓励良好的设计模式
  • 可能在未来版本中完全移除循环依赖支持

这个变更体现了Spring团队对代码质量架构设计的重视,虽然短期内会带来一些迁移成本,但长期来看有利于项目的可维护性和稳定性。


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

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

相关文章

本地代码上传Github步骤

1.注册Github账号 2.下载git客户端 下载、安装步骤可以参考网站&#xff1a;(6 封私信 / 10 条消息) 手把手教你用git上传项目到GitHub&#xff08;图文并茂&#xff0c;这一篇就够了&#xff09;&#xff0c;相信你一定能成功&#xff01;&#xff01; - 知乎 3.在Github上…

5G NR 非地面网络 (NTN) 5G、太空和统一网络

非地面网络 5G 和太空&#xff1a;对 NTN 测试与测量的影响NTN 基站测试与测量NTN 用户设备的测试设备R&SSMW200A 矢量信号发生器R&SSMBV100B 矢量信号发生器总结5G 和太空&#xff1a;对 NTN 测试与测量的影响 5G 非地面网络 (NTN) 是无线通信向全球性星基和机载通信…

少儿编程比赛(如蓝桥杯、创意编程大赛等)的题目类型、知识点及难度总结

以下是针对主流少儿编程比赛&#xff08;如蓝桥杯、创意编程大赛等&#xff09;的题目类型、知识点及难度总结&#xff0c;结合了Scratch和C等语言的真题分析&#xff0c;帮助备赛或教学参考&#xff1a; 一、基础操作与交互题&#xff08;适合6~10岁&#xff09; 考察图形化编…

SIFThinker: Spatially-Aware Image Focus for Visual Reasoning

SIFThinker: Spatially-Aware Image Focus for Visual Reasoning Authors: Zhangquan Chen, Ruihui Zhao, Chuwei Luo, Mingze Sun, Xinlei Yu, Yangyang Kang, Ruqi Huang 相关工作总结 视觉思维链推理 最近的研究表明&#xff0c;通过上下文学习逐步推理可以显著提升大型…

学习嵌入式第二十五天

IO 1.概念 IO指input/outputLinux中一切皆文件IO的操作对象是文件 2.文件一段数据的集合文件通常存放在外存中&#xff0c;掉电后数据不丢失分类b(block&#xff0c;块设备文件) 按块扫描信息的文件。通常存储类型的设备为块设备文件。文件IOc(character&#xff0c;字符设备文…

本地部署接入 whisper + ollama qwen3:14b 总结字幕

1. 实现功能 M4-1 接入 whisper ollama qwen3:14b 总结字幕 自动下载视频元数据如果有字幕&#xff0c;只下载字幕使用 ollama 的 qwen3:14b 对字幕内容进行总结 2.运行效果 &#x1f50d; 正在提取视频元数据… &#x1f4dd; 正在下载所有可用字幕… [youtube] Extracting U…

【13-向量化-高效计算】

研究者能够扩展神经网络并构建非常大型网络的原因之一&#xff0c;就是神经网络可以被向量化&#xff0c;vectorized&#xff1b;可以非常高效地用矩阵地乘法实现。 事实上&#xff0c;并行计算硬件&#xff0c;例如GPU&#xff0c;一些CPU的功能&#xff0c;非常擅长进行非常大…

论文中PDF的公式如何提取-公式提取

Mathcheap - An AI-powered, free alternative to Mathpix Snip. 从PDF中截图公式&#xff0c;之后 ctrl V 转换成功 &#xff0c;提取成功 复制到word中&#xff0c;是这样的 这显然不是我们需要的。 可以使用Axmath 复制进去Axmath 就能正常显示公式。 之后再插入word…

用 Flink SQL 和 Paimon 打造实时数仓:深度解析与实践指南

1. 实时数仓的魅力&#xff1a;从离线到分钟级的飞跃实时数仓&#xff0c;听起来是不是有点高大上&#xff1f;其实它没那么神秘&#xff0c;但确实能让你的数据处理能力像坐上火箭一样飙升&#xff01;传统的离线数仓&#xff0c;像 Hadoop 生态的 Hive&#xff0c;动辄小时级…

【已解决】报错:WARNING: pip is configured with locations that require TLS/SSL

一、问题背景二、问题分析1. SSL模块缺失的本质2. Anaconda环境特点三、问题表现四、解决方案详解1. 完整配置环境变量2. 添加环境变量的步骤3. 测试验证五、实战示例六、附加建议七、总结八、参考链接一、问题背景 在Windows 10系统中使用Python的包管理工具pip时&#xff0c…

Java项目基本流程(三)

一、页面初始化阶段&#xff08;加载即执行&#xff09;加载栏目列表&#xff08;同步请求&#xff09;发送同步 AJAX 请求到SearchChannel接口&#xff0c;获取所有栏目数据。清空下拉框&#xff08;.channelid&#xff09;后&#xff0c;先添加 “全部” 选项&#xff0c;再循…

鹧鸪云光伏仿真:项目前期决策的“数据明灯”

曾有一处光伏项目&#xff0c;在精心筹备数月后终于建成&#xff0c;却在运行初期即因未充分评估山体遮挡影响&#xff0c;导致实际发电量较预期大幅降低近一成。前期决策中的微小疏漏&#xff0c;往往成为项目经济性与可行性的致命伤。而鹧鸪云光伏仿真软件正是一盏照亮前路的…

开发指南129-基础类-BaseController

所有接口都需要继承BaseControllerBaseController里有很多有用的方法&#xff0c;现举例最重要的几个&#xff1a;1、getURI返回接口地址&#xff0c;就是PostMapping或GetMapping中定义的接口地址。常用于返回值中&#xff0c;例如接口的异常处理&#xff1a;try {// 处理逻辑…

C++高频知识点(十八)

文章目录86. C多线程中&#xff0c;锁的实现方式有哪些&#xff1f;1. 互斥锁&#xff08;Mutex&#xff09;2. 递归互斥锁&#xff08;Recursive Mutex&#xff09;3. 读写锁&#xff08;Shared Mutex&#xff09;4. 自旋锁&#xff08;Spinlock&#xff09;5. 条件变量&#…

【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day1

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a; 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》 ⭐️人生格言&#xff1a;生活是默默的坚持&#xff0c;毅力是永久的…

【软考中级网络工程师】知识点之 TCP 协议深度剖析

目录一、TCP 协议简介二、TCP 协议的特点2.1 面向连接2.2 可靠性高2.3 拥塞控制2.4 全双工通信2.5 高效性2.6 支持多种应用协议2.7 可靠的错误恢复三、TCP 协议的工作机制3.1 三次握手建立连接3.2 数据传输3.3 四次挥手关闭连接四、TCP 协议的数据包格式五、TCP 协议在实际应用…

操作系统1.5:操作系统引导

目录 总览 什么是操作系统引导&#xff1f; 磁盘里边有哪些相关数据? 操作系统引导(开机过程&#xff09; 总览 什么是操作系统引导&#xff1f; 操作系统引导(boot)——开机的时候&#xff0c;怎么让操作系统运行起来? 磁盘里边有哪些相关数据? 一个刚买来的磁盘(硬…

[鹧鸪云]光伏AI设计平台解锁电站开发新范式

1.[鹧鸪云]平台概述[鹧鸪云]是由鹧鸪云&#xff08;徐州&#xff09;信息技术有限公司倾力打造的&#xff0c;可以媲美‌PVsyst的光伏AI设计平台。它为光伏项目不同阶段的开发提供了快速设计、卫星地图设计、无人机3D设计、Unity3D设计、专业绘图设计与场区设计多种设计方式&am…

docker compose和docker-compose命令的区别

Docker Compose 有两种命令形式&#xff1a;docker compose&#xff08;空格连接&#xff09;docker-compose&#xff08;短横线连接&#xff09;其核心区别如下&#xff1a;一、技术特性docker-compose&#xff08;短横线&#xff09;独立可执行文件&#xff1a;作为独立程序安…

基于Strands Agent开发辅助阅读Agent

序 本篇由来&#xff0c;在COC上我当面感谢了组委会和姜宁老师&#xff0c;随即被姜宁老师催稿&#xff0c;本来当天晚上写了一个流水账&#xff0c;感觉甚为不妥。于是决定慢慢写&#xff0c;缓缓道来。要同时兼顾Show me the code&#xff0c;Show me the vide。希望能形成一…