0%

循环依赖

Spring 循环依赖深度解析:三级缓存的设计本质与 AOP 协同机制

Spring 对循环依赖的处理是其 IOC 容器设计的精髓之一,尤其是三级缓存的运用,不仅解决了依赖循环问题,更兼顾了 AOP 代理等复杂场景。从 “循环依赖分类→三级缓存工作机制→三级缓存必要性(为什么二级缓存不够)” 三个维度,彻底讲透 Spring 循环依赖的解决方案。

循环依赖的两种类型与处理差异

循环依赖指两个或多个 Bean 相互依赖形成闭环(如 A→B→A)。Spring 对不同注入方式的循环依赖处理方式截然不同:

1. 构造器循环依赖:无法解决,直接报错

当 Bean 通过构造器注入形成循环依赖时,Spring 无法解决,会抛出 BeanCurrentlyInCreationException

核心原因:实例化阶段就依赖对方,无提前暴露机会

构造器注入的依赖在 Bean 实例化阶段(调用构造器)就必须满足,而此时被依赖的 Bean 可能尚未实例化,形成 “先有鸡还是先有蛋” 的死锁。

源码验证:创建前的状态检查

Spring 通过 beforeSingletonCreation() 方法跟踪正在创建的 Bean,若检测到循环依赖,直接报错:

1
2
3
4
5
6
7
protected void beforeSingletonCreation(String beanName) {
// 若当前 Bean 已在创建中(存在于 singletonsCurrentlyInCreation),则抛出异常
if (!this.inCreationCheckExclusions.contains(beanName) &&
!this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

示例流程(A 构造器依赖 B,B 构造器依赖 A):

  1. 创建 A:调用 beforeSingletonCreation(A)singletonsCurrentlyInCreation 加入 A;
  2. A 构造器需要 B:调用 getBean(B)
  3. 创建 B:调用 beforeSingletonCreation(B)singletonsCurrentlyInCreation 加入 B;
  4. B 构造器需要 A:调用 getBean(A)
  5. 检测到 A 已在 singletonsCurrentlyInCreation 中,抛出异常。

2. Setter 循环依赖:单例模式下可解决,依赖三级缓存

当 Bean 通过Setter 方法注入形成循环依赖时,Spring 可通过 “提前暴露未完成初始化的 Bean” 解决,且仅支持单例 Bean(原型 Bean 无缓存,无法提前暴露)。

核心原理:将 “实例化” 与 “初始化” 分离,提前暴露实例
  • 实例化:通过构造器创建 Bean 空对象(未注入属性);
  • 提前暴露:将实例化后的空对象通过工厂放入三级缓存;
  • 初始化:注入属性时若依赖其他 Bean,可从缓存中获取对方的提前暴露实例。
示例流程(A Setter 依赖 B,B Setter 依赖 A):
  1. 创建 A:实例化(无参构造器)→ 提前暴露 A 到三级缓存 → 准备注入属性(需要 B);
  2. 创建 B:实例化(无参构造器)→ 提前暴露 B 到三级缓存 → 准备注入属性(需要 A);
  3. B 从三级缓存获取 A 的提前暴露实例 → 完成 B 的属性注入和初始化 → 放入一级缓存;
  4. A 从一级缓存获取 B 的完整实例 → 完成 A 的属性注入和初始化 → 放入一级缓存。

三级缓存的工作机制:从暴露到缓存升级

Spring 通过三级缓存实现 Setter 循环依赖的解决,三级缓存的角色和转换逻辑是核心:

1. 三级缓存的定义与分工

1
2
3
4
// DefaultSingletonBeanRegistry 中定义的三级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存:完整单例 Bean(实例化+初始化完成)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存:Bean 工厂(用于生成早期实例)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存:早期实例(实例化完成,初始化未完成)
缓存级别 存储内容 作用场景
一级缓存 完全初始化的单例 Bean 供外部获取最终可用的 Bean
二级缓存 实例化完成但未初始化的早期 Bean 缓存从三级缓存工厂生成的早期实例,避免重复生成
三级缓存 生成早期 Bean 的 ObjectFactory 延迟生成早期实例(含 AOP 代理逻辑)

2. 三级缓存的核心交互流程

以 A 依赖 B、B 依赖 A 的 Setter 循环为例,三级缓存的交互步骤如下:

步骤 1:A 实例化后,放入三级缓存

A 通过无参构造器实例化后,Spring 调用 addSingletonFactory() 将生成 A 早期实例的工厂放入三级缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// doCreateBean() 中触发
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 关键:通过后置处理器生成早期引用(可能是原始对象或 AOP 代理)
return getEarlyBeanReference(beanName, mbd, bean);
}
});

// 放入三级缓存的实现
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory); // 放入三级缓存
this.earlySingletonObjects.remove(beanName); // 清除二级缓存(互斥)
this.registeredSingletons.add(beanName);
}
}
}
步骤 2:A 需要 B,触发 B 的创建

A 进入属性注入阶段,发现需要 B,调用 getBean(B),B 重复 A 的流程:实例化→放入三级缓存→准备注入属性。

步骤 3:B 需要 A,从三级缓存获取 A 的早期实例

B 进入属性注入阶段,发现需要 A,调用 getBean(A),触发缓存查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// getSingleton() 缓存查找逻辑
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 查一级缓存:A 未完成初始化,无记录
Object singletonObject = this.singletonObjects.get(beanName);

// 2. A 正在创建中(isSingletonCurrentlyInCreation(A) 为 true)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 3. 查二级缓存:A 尚未进入二级缓存,无记录
singletonObject = this.earlySingletonObjects.get(beanName);

// 4. 允许早期引用,查三级缓存
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 获取 A 的工厂
if (singletonFactory != null) {
// 5. 调用工厂生成 A 的早期实例(可能是代理)
singletonObject = singletonFactory.getObject();
// 6. 放入二级缓存,移除三级缓存(升级缓存)
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
步骤 4:B 完成初始化,放入一级缓存

B 注入 A 的早期实例后,完成属性注入和初始化,调用 addSingleton(B) 放入一级缓存:

1
2
3
4
5
6
7
8
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject); // 放入一级缓存
this.singletonFactories.remove(beanName); // 清除三级缓存
this.earlySingletonObjects.remove(beanName); // 清除二级缓存
this.registeredSingletons.add(beanName);
}
}
步骤 5:A 注入 B 的完整实例,完成初始化

A 从一级缓存获取 B 的完整实例,完成属性注入和初始化,最终放入一级缓存。

为什么需要三级缓存?二级缓存不够吗?

这是理解循环依赖的关键问题。表面上看,二级缓存(earlySingletonObjects)似乎可以存储早期实例,但三级缓存(singletonFactories)的设计与 AOP 代理密切相关,是解决 “代理对象一致性” 的核心。

1. 没有 AOP 时,二级缓存确实可以解决循环依赖

若所有 Bean 都是普通对象(无需 AOP 代理),流程可简化为:

  • 实例化 A 后,直接将 A 的原始对象放入二级缓存;
  • B 依赖 A 时,从二级缓存获取 A 的原始对象;
  • 此时二级缓存足够,无需三级缓存。

但 Spring 必须支持 AOP 场景,此时三级缓存的必要性就凸显了。

2. 有 AOP 时,三级缓存是保证 “代理对象一致性” 的关键

当 Bean 需要被 AOP 代理时(如标注 @Transactional),最终放入一级缓存的是代理对象,而非原始对象。若使用二级缓存直接存储原始对象,会导致依赖方获取的是原始对象,与最终的代理对象不一致。

三级缓存通过 ObjectFactory 延迟生成代理对象,确保依赖方获取的是与最终一级缓存中一致的代理对象

关键逻辑:getEarlyBeanReference() 生成代理

三级缓存中 ObjectFactorygetObject() 方法会调用 getEarlyBeanReference(),该方法通过后置处理器(如 AbstractAutoProxyCreator)生成代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// AOP 代理在这里生成:若 Bean 需要代理,返回代理对象;否则返回原始对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
三级缓存解决的核心问题:代理对象的延迟生成与一致性
  • 延迟生成:代理对象不在实例化后立即生成,而是在首次被依赖时(循环依赖发生时)通过 ObjectFactory 生成,避免无循环依赖时的性能损耗;
  • 一致性:无论是否有循环依赖,最终放入一级缓存的和依赖方获取的都是同一个代理对象(若需要代理)。

反例:若用二级缓存直接存储原始对象

  • A 实例化后,将原始对象放入二级缓存;
  • B 依赖 A 时,从二级缓存获取原始对象并注入;
  • A 后续初始化时生成代理对象,放入一级缓存;
  • 最终 B 中注入的是 A 的原始对象,而容器中实际的 A 是代理对象,导致不一致。

3. 三级缓存的本质:平衡 “循环依赖解决” 与 “代理对象生成时机”

三级缓存的设计并非为了单纯存储,而是通过 ObjectFactory 实现了代理对象的按需生成

  • 无循环依赖时:ObjectFactory 不会被调用,代理对象在 Bean 初始化完成后生成(initializeBean() 阶段),放入一级缓存;
  • 有循环依赖时:ObjectFactory 被触发,提前生成代理对象并放入二级缓存,确保依赖方获取的是代理对象,与最终一级缓存中的对象一致。

总结:三级缓存的设计哲学

Spring 三级缓存解决循环依赖的核心逻辑可概括为:

  1. 分离实例化与初始化:允许 Bean 在实例化后、初始化前(未注入属性)就被暴露;
  2. 三级缓存分工:一级缓存存完整 Bean,二级缓存存早期实例,三级缓存存生成早期实例的工厂(含代理逻辑);
  3. AOP 协同:通过 ObjectFactory 延迟生成代理,确保循环依赖场景下代理对象的一致性

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10