Spring通过三级缓存解决循环依赖的机制,
1: 使用构造函数注入会报错
2:使用字段/Setter注入可以工作
# Spring三级缓存与循环依赖详解
## 一、Spring三级缓存机制
### 1. 三级缓存是什么?
Spring使用三个Map来解决循环依赖问题:
```java
// 在DefaultSingletonBeanRegistry类中定义
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的单例Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期的Bean引用(半成品,属性还未填充)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放Bean工厂,用于创建代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
// 正在创建的Bean名称集合
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}
```
### 2. Bean创建的关键流程
```java
// 简化的Bean创建流程
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1. 实例化(调用构造函数,此时Bean还是个"空壳")
Object beanInstance = instantiateBean(beanName, mbd);
// 2. 将刚实例化的Bean工厂放入三级缓存(关键步骤!)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性填充(依赖注入)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化(调用@PostConstruct等)
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 5. 从三级缓存移动到一级缓存
addSingleton(beanName, exposedObject);
}
```
---
## 二、不同注入方式的对比分析
### 场景设定:A和B循环依赖
```java
@Service
public class ServiceA {
private ServiceB serviceB;
// 不同的注入方式...
}
@Service
public class ServiceB {
private ServiceA serviceA;
// 不同的注入方式...
}
```
### 1. 构造函数注入(会报错)
```java
// 错误示例
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) { // 构造函数注入
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) { // 构造函数注入
this.serviceA = serviceA;
}
}
```
**执行流程分析:**
```mermaid
graph TD
A[开始创建ServiceA] --> B[实例化ServiceA]
B --> C{需要ServiceB参数}
C --> D[开始创建ServiceB]
D --> E[实例化ServiceB]
E --> F{需要ServiceA参数}
F --> G[获取ServiceA]
G --> H[ServiceA在创建中,但未放入缓存]
H --> I[❌ 抛出BeanCurrentlyInCreationException]
```
**关键问题:**
1. 构造函数注入发生在**实例化阶段**
2. 实例化时需要**立即获得依赖的Bean**
3. 此时被依赖的Bean还没有放入**三级缓存**(放入缓存是在实例化之后!)
4. Spring无法提供依赖,直接报错
### 2. 字段/Setter注入(可以工作)
```java
// 正确示例
@Service
public class ServiceA {
@Autowired // 字段注入
private ServiceB serviceB;
public ServiceA() {
// 构造函数可以正常执行
System.out.println("ServiceA实例化完成");
}
}
@Service
public class ServiceB {
@Autowired // 字段注入
private ServiceA serviceA;
public ServiceB() {
System.out.println("ServiceB实例化完成");
}
}
```
**执行流程分析(详细步骤):**
```mermaid
graph TD
A[1. 开始创建ServiceA] --> B[2. 实例化ServiceA<br/>调用无参构造函数]
B --> C[3. 将ServiceA的工厂放入三级缓存]
C --> D[4. 准备填充ServiceA的属性]
D --> E{5. 需要注入ServiceB}
E --> F[6. 开始创建ServiceB]
F --> G[7. 实例化ServiceB<br/>调用无参构造函数]
G --> H[8. 将ServiceB的工厂放入三级缓存]
H --> I[9. 准备填充ServiceB的属性]
I --> J{10. 需要注入ServiceA}
J --> K[11. 从缓存获取ServiceA]
K --> L{查询一级缓存}
L --> M[❌ 没有]
M --> N{查询二级缓存}
N --> O[❌ 没有]
O --> P{查询三级缓存}
P --> Q[✅ 找到ServiceA的工厂]
Q --> R[12. 执行工厂方法<br/>获取早期引用]
R --> S[13. 将早期引用放入二级缓存<br/>从三级缓存移除]
S --> T[14. ServiceB完成属性注入]
T --> U[15. ServiceB初始化完成]
U --> V[16. ServiceB放入一级缓存]
V --> W[17. 返回ServiceB给ServiceA]
W --> X[18. ServiceA完成属性注入]
X --> Y[19. ServiceA初始化完成]
Y --> Z[20. ServiceA放入一级缓存]
```
**详细文字流程:**
#### 第一阶段:创建ServiceA
1. **实例化ServiceA**:调用无参构造函数,创建原始对象 `a = new ServiceA()`
2. **放入三级缓存**:`addSingletonFactory("serviceA", () -> getEarlyBeanReference(...))`
- 注意:放入的是**ObjectFactory**(工厂),不是Bean本身
- 这个工厂可以在需要时创建ServiceA的早期引用(可能是代理对象)
#### 第二阶段:填充ServiceA的属性(发现需要ServiceB)
3. **开始创建ServiceB**:因为ServiceA依赖ServiceB
#### 第三阶段:创建ServiceB
4. **实例化ServiceB**:调用无参构造函数,创建原始对象 `b = new ServiceB()`
5. **放入三级缓存**:`addSingletonFactory("serviceB", ...)`
6. **填充ServiceB的属性**:发现需要ServiceA
#### 第四阶段:解决循环依赖(关键!)
7. **获取ServiceA的早期引用**:
- 从一级缓存找:没有(ServiceA还没初始化完成)
- 从二级缓存找:没有
- **从三级缓存找:找到工厂!**
- 执行工厂方法:`getEarlyBeanReference("serviceA")`
- 如果ServiceA需要AOP代理,这里会创建代理对象
- 如果不需要,直接返回原始对象
8. **放入二级缓存**:将得到的早期引用放入二级缓存 `earlySingletonObjects`
9. **从三级缓存移除**:从 `singletonFactories` 移除ServiceA的工厂
10. **完成ServiceB的属性注入**:将ServiceA的早期引用注入给ServiceB
11. **完成ServiceB的初始化**:调用`@PostConstruct`等
12. **ServiceB放入一级缓存**:ServiceB完全初始化完成
#### 第五阶段:完成ServiceA的创建
13. **完成ServiceA的属性注入**:将完全初始化好的ServiceB注入给ServiceA
14. **检查二级缓存**:ServiceA是否在二级缓存中(是的,在步骤8放入的)
15. **处理可能的代理**:如果二级缓存中的是代理对象,需要确保最终返回的是同一个代理
16. **完成ServiceA的初始化**
17. **ServiceA放入一级缓存**,从二级缓存移除
---
## 三、三级缓存的关键作用
### 1. 为什么需要三级缓存,而不是两级?
```java
// 如果没有三级缓存,只有两级的情况:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Async // 需要创建代理
public void method() {}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // 这里需要注入代理对象,不是原始对象
}
```
**问题场景:**
- ServiceA需要AOP代理(比如有`@Async`、`@Transactional`)
- ServiceB依赖ServiceA
- 如果在ServiceA实例化后**立即**放入二级缓存(原始对象)
- ServiceB会注入ServiceA的**原始对象**,而不是代理对象
- 这会导致`@Async`等AOP注解失效!
**三级缓存解决方案:**
- 放入三级缓存的是**ObjectFactory**
- 当需要获取早期引用时,才调用工厂创建
- 工厂方法会检查是否需要AOP,如果需要就创建代理
### 2. 代码演示缓存状态变化
```java
// 模拟缓存状态变化
public class CacheStateDemo {
// 创建ServiceA时
void createServiceA() {
// 步骤1: singletonObjects = {}
// earlySingletonObjects = {}
// singletonFactories = {}
// 步骤2: 实例化后
// singletonObjects = {}
// earlySingletonObjects = {}
// singletonFactories = {serviceA -> ObjectFactory}
// 步骤3: ServiceB需要ServiceA时
Object earlyA = singletonFactories.get("serviceA").getObject();
// singletonObjects = {}
// earlySingletonObjects = {serviceA -> earlyA}
// singletonFactories = {}
}
}
```
---
## 四、特殊情况和注意事项
### 1. 多级代理问题
```java
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
@Async
public void process() { // 需要多层代理
// ...
}
}
```
**Spring的处理:**
- 三级缓存工厂会处理所有代理逻辑
- 确保无论多少层AOP,最终注入的都是正确的代理对象
### 2. `@PostConstruct`中的循环依赖
```java
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@PostConstruct
public void init() {
serviceB.doSomething(); // 危险!ServiceB可能未完全初始化
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
@PostConstruct
public void init() {
// 可能依赖ServiceA的某些状态
}
}
```
**问题:** 虽然三级缓存解决了属性注入的循环依赖,但在`@PostConstruct`方法中调用对方方法,可能遇到NPE或状态不一致。
### 3. 原型(Prototype)作用域的Bean
```java
@Service
@Scope("prototype")
public class ServiceA {
@Autowired
private ServiceB serviceB; // 不支持循环依赖!
}
@Service
@Scope("prototype")
public class ServiceB {
@Autowired
private ServiceA serviceA; // 会报错!
}
```
**原因:** Spring不缓存原型Bean,无法提供早期引用。
---
## 五、实际开发建议
### 1. 最佳实践
```java
// 推荐:使用Setter注入,避免构造函数注入循环依赖
@Service
public class OrderService {
private PaymentService paymentService;
private InventoryService inventoryService;
// 必需的依赖使用构造函数注入
@Autowired
public OrderService(@NonNull PaymentService paymentService) {
this.paymentService = paymentService;
}
// 可选或可能循环的依赖使用Setter注入
@Autowired(required = false)
public void setInventoryService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
}
```
### 2. 循环依赖的检测工具
```java
@Component
public class CircularDependencyDetector implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 可以通过BeanFactory检查循环依赖
if (bean instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory factory = (ConfigurableBeanFactory) bean;
Set<String> inCreation = factory.getSingletonMutex();
// 分析创建中的Bean...
}
return bean;
}
}
```
### 3. 架构设计建议
1. **尽量避免循环依赖**:使用领域事件、中介者模式等解耦
2. **如果必须有循环依赖**:
- 使用Setter/字段注入
- 确保不要在`@PostConstruct`中调用对方
- 考虑使用`@Lazy`延迟加载
```java
@Service
public class ServiceA {
@Lazy // 延迟注入,打破循环
@Autowired
private ServiceB serviceB;
}
```
## 总结
理解Spring三级缓存机制的重要性:
1. **为什么构造函数注入不行**:因为依赖注入发生在实例化时,此时Bean还未放入缓存
2. **为什么字段/Setter注入可以**:因为依赖注入发生在属性填充阶段,此时Bean已在三级缓存中
3. **三级缓存的真正价值**:不仅解决循环依赖,还正确处理AOP代理
4. **设计启示**:良好的代码设计应该尽量避免循环依赖,而不是依赖框架的解决机制
这个理解能帮助你在遇到相关问题时快速定位,并设计出更健壮的Spring应用架构。