如何解决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提前在属性注入前完成,这样就不会导致后面生成的代理对象与属性注入时的对象的不一致。

















