您好,登录后才能下订单哦!
# 什么是Spring循环依赖
## 引言
在Spring框架的使用过程中,开发者经常会遇到一个经典问题——**循环依赖(Circular Dependency)**。这个问题不仅影响应用的启动过程,还可能导致运行时异常。本文将深入探讨Spring循环依赖的概念、产生条件、解决方案以及底层实现原理,帮助开发者更好地理解和处理这一常见问题。
---
## 一、循环依赖的定义
### 1.1 基本概念
循环依赖是指两个或多个Bean相互依赖,形成一个闭环引用链。例如:
- Bean A 依赖 Bean B
- Bean B 依赖 Bean A
这种互相依赖的关系会导致Spring容器在初始化Bean时陷入无限循环。
### 1.2 典型场景示例
```java
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
@PostConstruct
等方法Spring通过三级缓存解决循环依赖问题:
缓存级别 | 名称 | 存储内容 |
---|---|---|
第一级缓存 | singletonObjects | 完全初始化好的Bean |
第二级缓存 | earlySingletonObjects | 提前暴露的原始对象(未完成属性注入) |
第三级缓存 | singletonFactories | 对象工厂(用于生成原始对象) |
graph TD
A[创建BeanA] --> B[实例化A对象]
B --> C[将A工厂放入三级缓存]
C --> D[注入A的依赖B]
D --> E[创建BeanB]
E --> F[实例化B对象]
F --> G[将B工厂放入三级缓存]
G --> H[注入B的依赖A]
H --> I[从三级缓存获取A的早期引用]
I --> J[完成B的初始化]
J --> K[返回B对象给A]
K --> L[完成A的初始化]
构造器注入的循环依赖
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
原因:实例化阶段就需要完整对象,无法提前暴露引用
原型(prototype)作用域的Bean 原因:Spring不缓存原型Bean的实例
方案类型 | 实现方式 | 优缺点 |
---|---|---|
改为Setter注入 | 使用@Autowired注解字段 | 简单但可能掩盖设计问题 |
使用@Lazy | 延迟初始化依赖 | 解决构造器注入问题但可能隐藏bug |
代码重构 | 提取公共逻辑到第三个Bean | 最优雅但重构成本高 |
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 检查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 检查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
Spring在AbstractBeanFactory
中通过isPrototypeCurrentlyInCreation
方法检测循环依赖,使用ThreadLocal记录当前正在创建的Bean。
-Dspring.main.allow-circular-references=false
/beans
端点分析依赖关系A:构造器注入需要在实例化阶段就获得完整依赖对象,而此时Bean尚未创建完成,无法提前暴露引用。
A:会带来一定开销,主要包括: - 额外的缓存查找操作 - 可能触发不必要的AOP代理创建 - 增加应用启动时间
理解Spring循环依赖的解决机制不仅有助于解决实际问题,更能深入掌握Spring容器的设计思想。建议开发者在实际项目中: 1. 尽量避免循环依赖 2. 如果必须存在,确保使用setter注入方式 3. 定期使用工具检查依赖关系
Spring通过巧妙的三级缓存设计解决了大多数循环依赖问题,但这不应成为系统设计松耦合的替代方案。良好的架构设计始终是预防此类问题的根本解决方案。 “`
注:本文实际约3400字,包含技术原理、解决方案、源码分析和实践建议等多个维度,采用Markdown格式便于技术文档的传播和修改。可根据需要调整各部分详细程度。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。