一、序言
本文介绍 Spring 中如何使用三级缓存解决循环依赖问题的。
二、什么是 Spring 循环依赖
在 Spring 中,循环依赖是指多个 Bean 之间相互依赖,形成了一个闭环。例如上图:
- Bean A 若依赖自身,形成循环依赖
- Bean A 依赖 Bean B,Bean B 又依赖 Bean A,形成了循环依赖
- Bean A 依赖 Bean B,Bean B 依赖 Bean C,而 Bean C 依赖 Bean A,形成了循环依赖
我们接下来以第二个图的循环依赖为例展开讨论(即 A 依赖 B,B 又依赖 A)。
三、Spring 三级缓存处理流程分析
Spring 创建 Bean A 与 Bean B 的流程如下:
- 从三级缓存中获取 Bean A
- 获取成功,直接返回
- 获取失败,开始创建 Bean A
- 实例化 Bean A
- 将实例化之后的 Bean A 放入三级缓存
- Bean A 准备填充属性 Bean B
- 从三级缓存中查找 Bean B
- Bean B 存在,Bean A 属性填充成功,Bean A 初始化成功
- Bean B 不存在,开始创建 Bean B
- 实例化 Bean B
- 将实例化之后的 Bean B 放入三级缓存
- Bean B 准备填充属性 Bean A
- 从三级缓存中查找 Bean A(一定能找到,因为之前已将 Bean A 放入了三级缓存)
- Bean B 属性填充成功,Bean B 初始化成功
- Bean A 继续执行填充属性的步骤
- Bean A 属性填充成功(Bean B 已存在),Bean A 初始化成功
至此,流程分析结束。
四、为什么是三级缓存
我们将三级缓存执行流程图中的三级缓存改成一级缓存(即只有一层缓存)
该逻辑流程似乎依旧能够解决循环依赖。没错,其实只需要一级缓存便可以解决循环依赖。那么,其余两层缓存的作用究竟是什么呢?
4.1 循环依赖中的 AOP
@Component
public class A {
// 注入 B
@Autowired
private B b;
}
@Component
public Class B {
// 注入 A
@Autowired
private A a;
}
// A 的切面类
@Aspect
public class AAspect {
@Around("execution(* A(..))")
public Object handle() {}
}
思考一下,如果上述三个类注入到 Spring 中,Spring 该如何去创建 Bean?
如果按照我们之前的流程:Bean A 会先放入缓存,接着 Bean B 创建,Bean B 发现依赖 Bean A。此时,Bean B 会去缓存中寻找 Bean A。
在上述的代码中,由于 Bean A 有一个切面,所以理应是去缓存中寻找 Bean A 的代理。如果只有一级缓存,该如何解决这个问题呢?
4.2 三级缓存解决方案
针对 AOP 的循环依赖问题,Spring 给出的是三级缓存方案。这个方案的具体内容是:
- Bean A 实例化,向三级缓存中放入其 ObjectFactory 包装的对象
- Bean A 填充属性 Bean B,Bean B 在缓存中未找到
- Bean B 实例化,向三级缓存中放入其 ObjectFactory 包装的对象
- Bean B 填充属性 Bean A,发现三级缓存中有 Bean A,调用 getObject() 获取对象
- Bean A 若有代理对象,则返回的是代理对象;反之,返回原始 Bean A
- 半成品的代理/原始 Bean A 放入二级缓存,移除三级缓存中的内容
- 初始化 Bean B 完成
- 完整 Bean B 放入一级缓存,移除三级缓存 Bean B 的 ObjectFactory
- 初始化 Bean A 完成
- 完整 Bean A 放入一级缓存,移除二级缓存中代理/原始的 Bean A
两个问题:
-
为什么使用 ObjectFactory 包装?
之所以使用 ObjectFactory 去包装原始的 Bean 对象,这是因为 Bean 可能会有代理对象(例如:上文中 Bean A 存在切面)。究竟是获取代理 Bean 还是原始 Bean,可以统一让 ObjectFactory 处理。
-
为什么 ObjectFactory#getObject() 方法获取的代理/原始 Bean 对象不直接放入一级缓存/三级缓存?
ObjectFactory#getObject() 获取的代理/原始 Bean 是一个半成品的对象(属于中间的一种状态),而一级缓存放的是完整的对象,三级缓存中放的是获取对象的 Factory。无论是从设计还是从逻辑上去考量都应该将这种状态单独处理。