为了更好地理解 Spring 如何通过三级缓存解决循环依赖问题,我们可以通过一个具体的代码示例来展示循环依赖的场景,并结合 Spring 的三级缓存机制逐步分析其解决过程。
示例:循环依赖的 Bean
假设我们有两个类 BeanA
和 BeanB
,它们之间存在循环依赖关系:
BeanA
依赖于BeanB
BeanB
依赖于BeanA
代码示例
@Component
public class BeanA {
private final BeanB beanB;
@Autowired
public BeanA(BeanB beanB) {
System.out.println("BeanA 构造函数执行,注入 BeanB: " + beanB);
this.beanB = beanB;
}
public void print() {
System.out.println("BeanA 使用 BeanB: " + beanB);
}
}
@Component
public class BeanB {
private final BeanA beanA;
@Autowired
public BeanB(BeanA beanA) {
System.out.println("BeanB 构造函数执行,注入 BeanA: " + beanA);
this.beanA = beanA;
}
public void print() {
System.out.println("BeanB 使用 BeanA: " + beanA);
}
}
在这个例子中,BeanA
的构造函数需要注入 BeanB
,而 BeanB
的构造函数需要注入 BeanA
。这形成了一个循环依赖。
Spring 的解决流程
Spring 通过三级缓存机制解决了这种循环依赖问题。以下是详细的步骤和分析:
1. 创建 BeanA
- Spring 开始创建
BeanA
,并将其标记为“正在创建中”。 - Spring 调用
BeanA
的构造函数,创建其实例。 - 此时,
BeanA
的实例已经存在,但尚未完成属性注入(即beanB
尚未注入)。 - Spring 将
BeanA
的工厂对象(ObjectFactory
)放入 三级缓存(singletonFactories) 中。这个工厂对象负责生成BeanA
的早期引用。
关键点:此时,
BeanA
还没有完全初始化,但它已经在三级缓存中存在。
2. 注入 BeanB
- 在
BeanA
的创建过程中,Spring 发现BeanA
依赖于BeanB
,于是开始创建BeanB
。 - Spring 调用
BeanB
的构造函数,创建其实例。 - 同样地,Spring 将
BeanB
的工厂对象放入 三级缓存(singletonFactories) 中。
关键点:此时,
BeanB
也还没有完全初始化,但它已经在三级缓存中存在。
3. 注入 BeanA
- 在创建
BeanB
的过程中,Spring 发现BeanB
依赖于BeanA
,于是尝试获取BeanA
。 - 此时,
BeanA
还没有完全初始化完成,但它已经在 三级缓存(singletonFactories) 中存在。Spring 会从 三级缓存 中取出BeanA
的工厂对象,并通过该工厂对象创建一个“早期引用”(即尚未完全初始化的BeanA
实例),然后将这个“早期引用”放入 二级缓存(earlySingletonObjects) 中。 - 接着,Spring 将这个“早期引用”注入到
BeanB
中。
关键点:此时,
BeanB
已经获得了BeanA
的“早期引用”,尽管BeanA
还未完全初始化。
4. 完成 BeanB
的创建
BeanB
获得了BeanA
的“早期引用”后,继续完成自己的初始化过程(如属性填充、初始化方法等)。- 初始化完成后,
BeanB
被放入 一级缓存(singletonObjects) 中,表示它已经是一个完全初始化的 Bean。
关键点:此时,
BeanB
已经完全初始化,并且可以被其他 Bean 使用。
5. 完成 BeanA
的创建
- 回到
BeanA
的创建过程,此时BeanA
已经获得了BeanB
的引用(BeanB
已经完全初始化)。 BeanA
继续完成自己的初始化过程(如属性填充、初始化方法等),最后被放入 一级缓存(singletonObjects) 中,表示它已经是一个完全初始化的 Bean。
关键点:此时,
BeanA
和BeanB
都已经完全初始化,并且相互持有对方的引用。
输出结果
运行上述代码后,输出结果可能类似于以下内容:
BeanA 构造函数执行,注入 BeanB: null
BeanB 构造函数执行,注入 BeanA: com.example.BeanA@1a2b3c4d
可以看到:
- 在
BeanA
的构造函数执行时,BeanB
尚未完全初始化,因此BeanB
的引用为null
。 - 在
BeanB
的构造函数执行时,BeanA
的“早期引用”已经被注入,因此BeanA
的引用不为null
。
三级缓存的作用总结
-
一级缓存(singletonObjects):存放已经完全初始化完成的 Bean,确保每次获取的都是一个完整的、可用的 Bean。
-
二级缓存(earlySingletonObjects):存放提前暴露的、尚未完全初始化的 Bean 实例(早期引用)。当某个 Bean 还未完全初始化,但已经被其他 Bean 所依赖时,Spring 会将它的“早期引用”放入二级缓存中,供其他 Bean 使用。
-
三级缓存(singletonFactories):存放 Bean 的工厂对象,用于创建早期引用。当某个 Bean 还未完全初始化,但需要被其他 Bean 引用时,Spring 会通过工厂对象创建一个“早期引用”,并将其放入二级缓存中。
动态代理与三级缓存
如果 BeanA
或 BeanB
涉及到动态代理(如 AOP 代理),Spring 会在三级缓存中使用 ObjectFactory
来生成代理对象。这样即使在循环依赖的情况下,Spring 也能提前生成代理对象并暴露给其他 Bean 使用。
总结
通过这个实例,我们可以清楚地看到 Spring 是如何通过三级缓存机制解决循环依赖问题的:
- 三级缓存(singletonFactories):存放 Bean 的工厂对象,用于在 Bean 还未完全初始化时生成“早期引用”。
- 二级缓存(earlySingletonObjects):存放提前暴露的、尚未完全初始化的 Bean 实例(早期引用),以便在循环依赖的情况下,其他 Bean 能够使用这些“早期引用”。
- 一级缓存(singletonObjects):存放已经完全初始化完成的 Bean,确保最终每个 Bean 都是完整且可用的。
这种方式使得 Spring 能够在不破坏单例模式的前提下,处理复杂的循环依赖场景,包括涉及动态代理的情况。