如何解决Spring循环依赖问题?

循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。

1
2
3
4
5
6
7
public class AService {
private BService bService;
}

public class BService {
private AService 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

image-20231014162629222

三大循环依赖问题场景:

单例作用域下的Setter方法注入/Field属性注入出现的循环依赖。

  • Spring 中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制解决。

单例作用域下的构造器注入出现的循环依赖。

  • 因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存属性注入阶段
  • 也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。

原型作用域下的属性注入出现的循环依赖问题。

  • 因为 Spring 不会缓存 原型 作用域的 Bean,所以 Spring 无法解决 原型 作用域的 Bean。

三级缓存:

1
2
3
4
5
6
7
8
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

一级缓存(singletonObjects):缓存的是已经实例化、属性注入、初始化后Bean 对象。

二级缓存(earlySingletonObjects):缓存的是实例化后,但未属性注入、初始化Bean对象(用于提前暴露 Bean)。

三级缓存(singletonFactories):

  • 缓存的是一个 ObjectFactory
  • 主要作用是生成原始对象进行 AOP 操作后的代理对象(这一级缓存主要用于解决 AOP 问题)。

为什么缓存可以解决循环依赖问题?

在创建 AService 时,实例化后将 原始对象 存放到缓存中(提早暴露)。

然后依赖注入时发现需要 BService,便会去创建 BService

实例化后同样将 原始对象 存放到缓存中,然后依赖注入时发现需要 AService 便会从缓存中取出并注入。

这样 BService 就完成了创建,随后 AService 也就能完成属性注入,最后也完成创建。

image-20231014163323751

为什么还需要第三级缓存?

第三级缓存(singletonFactories 是为了处理 SpringAOP的。

如果 AService 中方法没有使用 AOP 操作。

会发现 BService 注入的 原始对象 与最后 AService 完成创建后的最终对象是同一个对象

如果 AService 方法中有 AOP 操作时,当 AService 的原始对象赋值(注入)给 BService

AService 会进行 AOP 操作产生一个 代理对象,这个代理对象最后会被放入单例池(一级缓存)中。

也就是说此时 BService 中注入的对象是原始对象,而 AService 最终创建的完成后是代理对象。

这样就会导致 BService 依赖的 AService 和 最终的 AService 不是同一个对象

出现这个问题原因:

AOP 是通过 BeanPostProcessor 实现的,而 BeanPostProcessor 是在 属性注入阶段后 才执行的。

所以会导致注入的对象有可能和最终的对象不一致

image-20231014163541994

Spring 是如何通过第三级缓存来避免 AOP 问题的?

三级缓存通过利用 ObjectFactorygetEarlyBeanReference() 做到了提前执行 AOP 操作从而生成代理对象。

在上移到二级缓存时,可以做到如果 Bean 中有 AOP 操作。

那么提前暴露的对象会是 AOP 操作后返回的代理对象;如果没有 AOP 操作,那么提前暴露的对象会是原始对象。

  • 只有等完成了属性注入、初始化后的 Bean 才会上移到一级缓存(单例池)中。

这样就能做到出现循环依赖问题时,注入依赖的对象和最终生成的对象是同一个对象。

  • 相当于 AOP 提前在属性注入前完成,这样就不会导致后面生成的代理对象与属性注入时的对象的不一致。