郑州做网站推市场营销互联网营销
循环依赖问题是在对象之间存在相互依赖关系,形成一个闭环,导致无法准确的完成对象的创建和初始化,当两个或多个对象彼此之间相互引用,这种相互引用形成一个循环时,就可能出现循环依赖问题。
在 Spring 框架中,循环依赖(Circular Dependency)是指两个或多个 Bean 之间互相依赖,导致依赖关系形成一个闭环。例如:
- Bean A 依赖 Bean B。
- Bean B 又依赖 Bean A。
这种依赖会导致 Spring 无法正确实例化和初始化这些 Bean,因为在创建一个 Bean 时需要先创建它所依赖的 Bean,而它依赖的 Bean 又反过来依赖它
如何解决循环依赖
单例作用域的循环依赖,依赖于三级缓存来解决问题
- 一级缓存(Singleton Objects): 存放已经完全初始化的单例 Bean。
- 二级缓存(Early Singleton Objects): 存放原始的、尚未完全初始化的 Bean 实例(暴露了部分 Bean 的引用)。
- 三级缓存(Singleton Factories): 存放 Bean 的对象工厂,延迟初始化
解决循环依赖的前提条件
- 互相依赖的Bean必须要是单例的Bean
- 依赖注入的方式不能都是构造函数注入的方式
当遇到循环依赖时,Spring 采取以下步骤
1.创建 Bean 的原始实例:
- Spring 首先根据构造器或工厂方法创建 Bean 的原始实例(即还未完成依赖注入和初始化)。
- 这个实例此时不会立即暴露给其他 Bean。
- 将
ObjectFactory
放入三级缓存: - Spring 会将一个能够生成该 Bean 的工厂对象(
ObjectFactory
)放入三级缓存(singletonFactories
)。
2.检测并解决依赖:
- 如果依赖的 Bean 已经创建,则直接从一级缓存(
singletonObjects
)中获取。 - 如果依赖的 Bean 尚未完全初始化但已创建原始实例,Spring 会从三级缓存中通过
ObjectFactory
获取早期引用,并将其放入二级缓存(earlySingletonObjects
)。 - 从二级缓存中获取的 Bean 引用可能是代理对象,用于避免后续初始化重复。
3.完成依赖注入:
- 使用从一级缓存或二级缓存中获取的 Bean 引用,完成当前 Bean 的依赖注入。
4.完成 Bean 初始化:
- 初始化后,Spring 会将完全初始化的 Bean 放入一级缓存(
singletonObjects
),并从二级缓存和三级缓存中移除相关引用
为什么一定要使用三级缓存
1.首先,一级缓存一定是要的,因为Bean是单例模式,需要存放到某个容器中,而一级缓存就是这个缓存容器。
2.二级缓存可以不要,我们可以把半成品Bean放到一级缓存中,但这样需要给一级缓存中添加标识,标识哪些是完整对象,哪些都是半成品对象,这样有几个问题: 1.增加了Spring源码设计复杂性。 2.在查询时,需要先判断标识,查询效率变低了。 3.违反单一设计原则。
因此为了解决这些问题,二级缓存也是必须要的。
3.三级缓存也可以不要,但这样也有一个问题,那就是Spring的设计模式中,在生成代理时,为了实现对象初始化和生成代理对象的解耦 所以代理对象是在AnnotationAwareAspectJAutoProxyCreator 这个后置处理的最后一步生成AOP代理对象的。
如果不要三级缓存,那么我们需要在所有类创建之前,先将代理类创建出来,这样遇到循环依赖就可以直接拿出代理对象来使用了,但这种方式的缺点是打破了原理Spring 的设计理念。(实现对象初始化和生成代理对象的解耦)
所以最优方案是,不提前创建代理对象,而是使用三级缓存存储创建对象的表达式,等遇到循环依赖,再按照Spring的设计模式来生成代理对象。