什么是循环依赖
什么是循环依赖
现在我们有两个类 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 可以引用。