3. 为什么 Spring 循环依赖需要三级缓存,二级不够吗?
大约 4 分钟
在 Spring 框架中,解决单例 Bean 循环依赖的问题时,使用了三级缓存机制。理解为什么需要三级缓存以及为什么二级缓存不够,涉及到 Spring 的 Bean 创建和初始化过程。下面详细解释。
1. Spring 中的三级缓存
在 Spring 的 DefaultSingletonBeanRegistry
类中,有三个用于缓存单例 Bean 的 Map,分别是:
- 一级缓存:
singletonObjects
- 这是 Spring 中最常见的单例缓存,用于存放完全初始化好的单例 Bean,可以直接使用。
- 二级缓存:
earlySingletonObjects
- 用于存放早期暴露的单例 Bean,Bean 已经实例化但尚未初始化完成。这用于解决循环依赖问题。
- 三级缓存:
singletonFactories
- 用于存放单例 Bean 工厂对象(
ObjectFactory
),可以在需要时为 Bean 创建实例并将其暴露。
- 用于存放单例 Bean 工厂对象(
2. 为什么需要三级缓存?
三级缓存的主要目的是解决以下问题:
- 循环依赖问题:在循环依赖的场景下,一个 Bean 在初始化时可能会依赖另一个尚未初始化完成的 Bean。Spring 的三级缓存允许在 Bean 初始化完成之前,将一个尚未完全初始化的 Bean 以“早期引用”的方式暴露出来,从而破除循环依赖。
- AOP 代理问题:当 Bean 涉及 AOP 代理(比如使用
@Transactional
或@Aspect
)时,Spring 在创建 Bean 的时候可能会为 Bean 创建一个代理对象。如果只有二级缓存,无法在 Bean 初始化之前暴露经过代理的早期引用。而三级缓存允许在真正需要 Bean 的时候,通过工厂方法生成代理对象并暴露,这样可以确保注入的 Bean 是正确的代理对象。
3. 三级缓存的工作机制
- 第一级缓存:
singletonObjects
保存的是完全初始化好的单例 Bean。 - 第二级缓存:
earlySingletonObjects
保存的是早期暴露的 Bean 引用,用于解决循环依赖。 - 第三级缓存:
singletonFactories
保存的是ObjectFactory
,用于在 Bean 初始化过程中创建代理对象或其他特殊处理。
4. 三级缓存的典型使用场景
假设存在以下循环依赖场景:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
步骤:
- Spring 创建
ServiceA
的实例,进入初始化过程。 ServiceA
依赖ServiceB
,因此 Spring 尝试创建ServiceB
实例。- 在创建
ServiceB
的实例时,发现它依赖ServiceA
,但ServiceA
还没有完成初始化。 - Spring 通过三级缓存机制,先将
ServiceA
的一个早期引用暴露出来(此时ServiceA
可能还未完成初始化)。 ServiceB
可以注入ServiceA
的早期引用并完成初始化。ServiceA
的初始化完成,将其放入一级缓存,并将其从二级缓存和三级缓存中移除。
在这个过程中,三级缓存允许 Spring 在必要时生成早期引用,并将其代理暴露给依赖它的 Bean,从而解决循环依赖问题。
5. 二级缓存不够的原因
如果只有二级缓存,则无法灵活处理 AOP 代理对象的创建与暴露。对于涉及 AOP 代理的 Bean,二级缓存中的 Bean 引用在代理创建之前暴露,可能会导致注入的 Bean 不是代理对象,而是未增强的原始对象。
三级缓存通过 ObjectFactory
在真正需要时创建并暴露代理对象,确保 Bean 的引用始终是正确的代理对象。因此,二级缓存无法解决所有循环依赖问题,特别是涉及到 AOP 代理的场景。
6. 总结
三级缓存机制是 Spring 解决单例 Bean 循环依赖的关键手段。它不仅能有效处理常见的循环依赖问题,还能确保在 AOP 场景下,正确地暴露代理对象。如果只有二级缓存,Spring 在处理复杂依赖关系时会受到限制,可能会导致不正确的 Bean 注入。因此,三级缓存机制是必要的。