什么是循环依赖
现在我们有两个类 ClassA 与 ClassB,但他们互相引用,直接或间接依赖对方,例如例如A类里有B的对象,B类中又有A的对象
public class ClassA {
private ClassB classB;
// 构造方法、getter 和 setter 等
}
public class ClassB {
private ClassA classA;
// 构造方法、getter 和 setter 等
}```
这种依赖不仅限于出现在类上,也可能会出现在包上或者模块上
- **出现在包层面的危害:
- 增加了代码的耦合度,降低了代码的可维护性和可读性
- 在编译时可能会出现编译顺序的问题,难以确定先编译哪个包。
- 在后续的开发和维护过程中,增加了维护的难度和风险。
- **出现在模块层面的危害:
- 会导致模块之间的边界变得模糊,无法清晰地划分模块的职责。在构建和部署项目时,可能会出现循环加载的问题,影响项目的启动效率和运行性能。
## Spring中的循环依赖
当两个或多个 Bean 之间存在构造器注入的循环依赖时,Spring 无法解决这种依赖关系。例如,`BeanA` 的构造器需要 `BeanB`,而 `BeanB` 的构造器又需要 `BeanA`。Spring 容器在创建这些 Bean 的过程中会陷入死循环,无法确定先创建哪个 Bean,从而抛出 `BeanCurrentlyInCreationException` 或 `BeanCurrentlyInCreationException` 等异常。
解决方法:
- Spring 通过单例 Bean 的提前暴露机制来解决基于 Setter 注入的循环依赖问题
- **创建 BeanA 的壳对象并存入三级缓存** :当创建 `BeanA` 时,Spring 会先创建一个 `BeanA` 的壳对象(即一个未完全初始化的实例,只是构造函数执行完成,属性还没有填充),并将其存入三级缓存(`SingletonObjects`、`earlySingletonObjects` 和 `singletonFactories` 组成的缓存体系)中的 `singletonFactories`。
- **创建 BeanB 并注入依赖** :接着创建 `BeanB`,在创建 `BeanB` 的过程中,发现 `BeanB` 依赖 `BeanA`,此时 Spring 会尝试从缓存中获取 `BeanA`。虽然 `BeanA` 还没有完全初始化(只是壳对象在 `singletonFactories` 中),但 Spring 会将其从 `singletonFactories` 中取出,放入 `earlySingletonObjects` 中,并将其作为依赖注入到 `BeanB` 中。
- **完成 BeanA 的初始化并注入到 BeanB** :`BeanA` 的壳对象被注入到 `BeanB` 后,Spring 会继续完成 `BeanA` 的初始化(填充属性等操作)。当 `BeanA` 初始化完成后,会将它放入 `SingletonObjects` 中。在后续的操作中,如果 `BeanB` 还需要访问 `BeanA`,可以直接从 `SingletonObjects` 中获取已经完全初始化的 `BeanA`。
## 三级缓存
### 一级缓存(`singletonObjects`)
一级缓存也被称为单例池,存储的是已经完全初始化好的单例 Bean 实例。当需要获取一个单例 Bean 时,Spring 会优先从这个缓存中查找,其数据结构为 `Map<String, Object>`,键是 Bean 的名称,值是对应的 Bean 实例。
### 二级缓存(`singletonFactories`)
当 Bean 实例化完成,但还未完成属性注入和初始化时,会将一个创建该 Bean 代理对象的工厂存入二级缓存。其数据结构为 `Map<String, ObjectFactory<?>>`,键为 Bean 的名称,值是用于创建 Bean 的工厂对象。如果从一级缓存中未找到所需的 Bean,Spring 会尝试从二级缓存中获取对应的工厂对象,并通过该工厂创建 Bean 的早期暴露实例。
### 三级缓存(`earlySingletonObjects`)
三级缓存存储的是提前暴露的单例 Bean 实例,这些 Bean 虽然还未完成全部的初始化流程,但已经可以被引用。通过这个缓存,其他 Bean 在依赖该 Bean 时可以获取到一个早期的实例。其数据结构为 `Map<String, Object>`,键是 Bean 的名称,值是早期暴露的 Bean 实例。如果从二级缓存中获取到工厂对象后,会使用该工厂生成一个早期的 Bean 实例,并将其存入三级缓存中,以便其他 Bean 可以引用。