当前位置: 首页 > news >正文

Spring循环依赖详解

Spring 循环依赖详解

一、循环依赖

1. 什么是循环依赖

循环依赖是指两个或多个Spring Bean之间形成相互依赖的闭环关系。具体表现为:
Bean A 依赖 Bean B
Bean B 依赖 Bean C

Bean N 又依赖 Bean A

2. 循环依赖出现的场景

循环依赖通常出现在以下场景中:

  • Bean初始化阶段
  • 当Spring容器启动,初始化Bean时
  • 特别是使用构造器注入方式时最容易暴露
  • 依赖注入时
  • 通过@Autowired进行字段/方法注入时
  • 使用XML配置中的或时
  • 特定设计模式中
  • 双向关联的业务场景(如订单-支付系统)
  • 相互回调的组件设计

3. 循环依赖会带来什么问题

  • 启动时抛出BeanCurrentlyInCreationException
  • 错误信息:“Requested bean is currently in creation: Is there an unresolvable circular reference?”
  • 每次获取bean都会尝试创建新实例
  • 最终导致栈溢出(StackOverflowError)

二、循环依赖的几种类型

1. Setter/Field注入循环依赖

@Component
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}@Component
public class ServiceB {@Autowiredprivate ServiceA serviceA;
}

2. 构造器循环依赖

@Component
public class ServiceA {private final ServiceB serviceB;public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }
}@Component
public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; }
}

三、Spring解决循环依赖的机制

1. 三级缓存机制(可解决注入循环依赖)

1.1 三级缓存

在初始化阶段(实例化之后),Spring使用三级缓存来解决单例Bean的循环依赖问题:

缓存级别名称存储内容作用
一级缓存singletonObjects完全初始化好的Bean提供最终可用的Bean
二级缓存earlySingletonObjects提前曝光的半成品Bean解决循环依赖
三级缓存singletonFactories对象工厂(ObjectFactory)生成代理对象

这是Spring三级缓存的部分源码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {/** Cache of singleton objects: bean name to bean instance. *///  一级缓存private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. *///  三级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. *///  二级缓存private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);...
}
1.2 解决流程(以A→B→A为例)
  1. 开始创建A,实例化A(调用构造器)
  2. 将A的对象工厂放入三级缓存
  3. 填充A的属性,发现需要B
  4. 开始创建B,实例化B(调用构造器)
  5. 将B的对象工厂放入三级缓存
  6. 填充B的属性,发现需要A
  7. 从三级缓存获取A的对象工厂,生成早期引用
  8. 将A的早期引用放入二级缓存,删除三级缓存中的A
  9. B完成属性注入,初始化完成
  10. A得到B的引用,继续完成初始化
  11. A初始化完成,放入一级缓存
  12. B中的A引用最终指向完全初始化的A

在这里插入图片描述

1.3 关键源码分析

DefaultSingletonBeanRegistry类中的关键方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 从一级缓存查询Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 2. 从二级缓存查询singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 3. 从三级缓存获取ObjectFactoryObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

2. 懒加载(可解决构造循环依赖)

在实例化阶段,可使用@Lazy注解通过以下方式解决构造器循环依赖:
不立即初始化依赖的Bean
创建一个代理对象作为占位符
当实际需要调用依赖Bean时才进行初始化

@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(@Lazy ServiceB serviceB) {this.serviceB = serviceB;}
}@Service
public class ServiceB {private final ServiceA serviceA;public ServiceB(@Lazy ServiceA serviceA) {this.serviceA = serviceA;}
}

四、如何避免/解决循环依赖

1. 设计层面解决方案

  1. 重构代码:解耦相互依赖的组件
  2. 接口抽象:依赖接口而非具体实现
  3. 应用事件:使用事件机制代替直接调用
  4. 模板方法模式:将公共逻辑提取到父类

2. 技术层面解决方案

  1. 使用@Lazy延迟加载
@Component
public class ServiceA {@Lazy@Autowiredprivate ServiceB serviceB;
}
  1. 使用Setter注入代替构造器注入
@Component
public class ServiceA {private ServiceB serviceB;@Autowiredpublic void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
}
  1. 使用@DependsOn指定初始化顺序
@Component
@DependsOn("serviceB")
public class ServiceA {@Autowiredprivate ServiceB serviceB;
}
  1. 使用ApplicationContextAware手动获取Bean
@Component
public class ServiceA implements ApplicationContextAware {private ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext context) {this.context = context;}public void doSomething() {ServiceB serviceB = context.getBean(ServiceB.class);// 使用serviceB}
}

五、特殊场景处理

1. AOP代理下的循环依赖

Spring通过SmartInstantiationAwareBeanPostProcessor处理代理对象的循环依赖,关键类是AbstractAutoProxyCreator

2. 构造器注入的替代方案

如果必须使用构造器注入,可以考虑:

@Configuration
public class MyConfig {@Beanpublic ServiceA serviceA(@Lazy ServiceB serviceB) {return new ServiceA(serviceB);}
}

六、最佳实践建议

  1. 尽量避免循环依赖,这是代码设计问题而非技术问题
  2. 优先使用构造器注入(Spring官方推荐)
  3. 对于不可避免的循环依赖,使用Setter/Field注入
  4. 在大型项目中,使用模块化设计减少循环依赖
  5. 定期使用架构分析工具检测循环依赖
http://www.lryc.cn/news/616385.html

相关文章:

  • MySQL面试题及详细答案 155道(041-060)
  • LeeCode 46. 全排列
  • 冒泡排序实现以及优化
  • 20250810 | 深度学习入门笔记1
  • 大型动作模型LAM:让企业重复任务实现80%效率提升的AI技术架构与实现方案
  • 五种 IO 模型与阻塞 IO
  • 数组中的第K个最大元素
  • MyBatisPlus插件原理
  • Leetcode 3646. Next Special Palindrome Number
  • 代码随想录算法训练营第六十天|图论part10
  • 【Nginx②】 | Nginx部署前端静态文件指南(基于虚拟机环境)
  • 浏览器CEFSharp88+X86+win7 之多页面展示(四)
  • NodeJs学习日志(4):路由合并_环境配置_常用文件目录
  • element-ui el-progress在有小数的情况下,会换行显示。解决不换行的问题。
  • iceberg安装部署
  • Rust面试题及详细答案120道(11-18)-- 控制流与函数
  • vulnhub-Drippingblues靶机
  • 通过Certbot自动申请更新HTTPS网站的SSL证书
  • 瑞芯微 RK3588 平台驱动开发 学习计划
  • CST支持对哪些模型进行特征模仿真?分别有哪些用于特征模分析的求解器?
  • C语言——深入理解指针(二)
  • 【东枫科技】FR3 可扩展测试平台,适用于 6G 研究与卫星通信,高达 1.6 GHz 的带宽
  • 【秋招笔试】2025.08.09美团秋招算法岗机考真题-第三题
  • Python 的浅拷贝 vs 深拷贝(含嵌套可变对象示例与踩坑场景)
  • OpenGL VAO 概念、API 和示例
  • 每日一题:使用栈实现逆波兰表达式求值
  • TypeScript中的type和interface的区别是什么?
  • 从街亭失守看管理
  • WAV音频数据集MFCC特征提取处理办法
  • 【MySQL——第三章 :MySQL库表操作】