如何解决Spring循环依赖问题?
如何解决Spring循环依赖问题?
月伴飞鱼循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
1 | public class AService { |
Bean
的创建步骤:
在创建
Bean
之前,Spring
会通过扫描获取BeanDefinition
。
BeanDefinition
就绪后会读取BeanDefinition
中所对应的Class
来加载类。实例化阶段:根据构造函数来完成实例化 (未属性注入以及初始化的对象(原始对象))。
属性注入阶段:对
Bean
的属性进行依赖注入。如果
Bean
的某个方法有AOP
操作,则需要根据原始对象生成代理对象。
AOP
代理是BeanPostProcessor
实现的,而BeanPostProcessor
是发生在属性注入阶段后的。最后把代理对象放入单例池(一级缓存
singletonObjects
)中。
为什么 Spring Bean
会产生循环依赖问题?
当
AService
创建时,会先对AService
实例化生成一个原始对象。然后在进行属性注入时发现了需要
BService
对应的Bean
。此时就会去为
BService
进行创建,在BService
实例化后生成一个原始对象后进行属性注入。此时会发现也需要
AService
对应的Bean
。
三大循环依赖问题场景:
单例作用域下的Setter方法注入/Field属性注入出现的循环依赖。
- 在
Spring
中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制解决。单例作用域下的构造器注入出现的循环依赖。
- 因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存 在 属性注入阶段。
- 也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。
原型作用域下的属性注入出现的循环依赖问题。
- 因为
Spring
不会缓存 原型 作用域的Bean
,所以Spring
无法解决 原型 作用域的 Bean。
三级缓存:
1 | /** Cache of singleton objects: bean name to bean instance. */ |
一级缓存(
singletonObjects
):缓存的是已经实例化、属性注入、初始化后的Bean
对象。二级缓存(
earlySingletonObjects
):缓存的是实例化后,但未属性注入、初始化的Bean
对象(用于提前暴露Bean
)。三级缓存(
singletonFactories
):
- 缓存的是一个
ObjectFactory
。- 主要作用是生成原始对象进行
AOP
操作后的代理对象(这一级缓存主要用于解决AOP
问题)。
为什么缓存可以解决循环依赖问题?
在创建
AService
时,实例化后将 原始对象 存放到缓存中(提早暴露)。然后依赖注入时发现需要
BService
,便会去创建BService
。实例化后同样将 原始对象 存放到缓存中,然后依赖注入时发现需要
AService
便会从缓存中取出并注入。这样
BService
就完成了创建,随后AService
也就能完成属性注入,最后也完成创建。
为什么还需要第三级缓存?
第三级缓存(
singletonFactories
) 是为了处理Spring
的AOP
的。如果
AService
中方法没有使用AOP
操作。会发现
BService
注入的 原始对象 与最后AService
完成创建后的最终对象是同一个对象。如果
AService
方法中有AOP
操作时,当AService
的原始对象赋值(注入)给BService
。
AService
会进行AOP
操作产生一个 代理对象,这个代理对象最后会被放入单例池(一级缓存)中。也就是说此时
BService
中注入的对象是原始对象,而AService
最终创建的完成后是代理对象。这样就会导致
BService
依赖的AService
和 最终的AService
不是同一个对象。出现这个问题原因:
AOP 是通过
BeanPostProcessor
实现的,而BeanPostProcessor
是在 属性注入阶段后 才执行的。所以会导致注入的对象有可能和最终的对象不一致。
Spring
是如何通过第三级缓存来避免 AOP
问题的?
三级缓存通过利用
ObjectFactory
和getEarlyBeanReference()
做到了提前执行AOP
操作从而生成代理对象。在上移到二级缓存时,可以做到如果
Bean
中有AOP
操作。那么提前暴露的对象会是
AOP
操作后返回的代理对象;如果没有AOP
操作,那么提前暴露的对象会是原始对象。
- 只有等完成了属性注入、初始化后的
Bean
才会上移到一级缓存(单例池)中。这样就能做到出现循环依赖问题时,注入依赖的对象和最终生成的对象是同一个对象。
- 相当于
AOP
提前在属性注入前完成,这样就不会导致后面生成的代理对象与属性注入时的对象的不一致。