跳转至

循环依赖

约 908 个字 17 行代码 4 张图片 预计阅读时间 3 分钟

什么是循环依赖

首先了解一下 Bean 的初始化流程,单例 Bean 从创建到完成,主要经历了三步。

image.png

循环依赖可以用一个例子来理解,在 Spring 中,a 依赖 b,b 依赖a,形成了一个闭环,在这种情况下,初始化任意一个 Bean,都会导致最终又依赖回自己,导致初始化失败。

解决循环依赖流程

spring 用了三级缓存解决循环依赖。

  • 一级缓存: Map<String, Object> singletonObjects 是成品对象单例池,用于保存实例化、属性赋值 (注入)、初始化完成的 bean 实例
  • 二级缓存: Map<String, Object> earlySingletonObjects 存储早期曝光对象,用于保存实例化完成的 bean 实例
  • 三级缓存: Map<String, ObjectFactory<?>> singletonFactories 是早期曝光对象工厂,保存 bean 创建工厂(ObjectFactory)
Java
  /**
 * 单例对象集合,存储 BeanName - Bean对象 键值对
 * Cache of singleton objects: bean name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

/**
 * 二级缓存,提前暴露对象,没有完全实例化的对象
 * Cache of singleton objects: bean name --> bean instance
 */
protected final Map<String, Object> earlySingletonObjects = new HashMap<>();

/**
 * 三级缓存,存放代理对象
 * Cache of singleton factories: bean name --> ObjectFactory
 */
protected final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

一级缓存作用

在获取对象的时候,会调用 getSingleton(beanName) 方法,从一级缓存获取 Bean,如果不存在,就会调用重载方法 getSingleton(beanName, 创建方法) 创建 Bean 后,将它设置回一级缓存。


三级缓存作用

在 createBean 方法中,调用具体的 doCreateBean 方法

主要做了如下几件事

  1. 实例化对象
  2. 将对象的创建工厂保存在三级缓存中
  3. 属性赋值
  4. 初始化处理 (前置,初始化方法,后置)

image.png

对象工厂做了什么

本质上的作用是暴露一个早期对象。把所有实现了 InstantiationAwareBeanPostProcessor (Spring 源码中为 SmartInstantiationAwareBeanPostProcessor ) 的后置处理器提前运行。AOP 在这里提前执行。

image.png

可以这么说, SmartInstantiationAwareBeanPostProcessor 就是为了解决循环依赖而生的。


二级缓存作用

在属性赋值阶段,如果出现了循环依赖,解决循环依赖的流程如下:

  1. A 实例化成功

  2. A 把自己的 ObjectFactory 保存在三级缓存

  3. A 属性赋值依赖 B

  4. B 实例化成功

  5. B 把自己的 ObjectFactory 保存在三级缓存中

  6. B 属性复制依赖 A,这时候 B 调用的是复用的 getSingleton(beanName) 方法,底层会先去一二级缓存中找 A,如果都找不到,会找到三级缓存,然后调用对象工厂的获取对象方法,获取代理后或者没有代理的对象(这个取决于 wrapIfNecessary 逻辑),最后把得到的对象放到二级缓存中,删除三级缓存。

  7. 之后如果 C 也依赖了 A,那么也能在二级缓存中找到这个代理对象。一旦 A 初始化完毕,会将自己放入一级缓存中,同时删除二级缓存。

image.png

只用一二级缓存,能否解决循环依赖

理论上,只用一二级缓存就能解决循环依赖问题了,我们可以抛弃三级缓存,我们知道三级缓存的 value 本质上是一个对象工厂,那么是不是可以在对象实例化的时候,都去执行一遍 ObjectFactory 的创建对象方法,将代理对象存储在二级缓存里即可。

三级缓存其实是和 spring 规范挂钩的。

因为 aop 的实现是靠实现了 SmartInstantiationAwareBeanPostProcessor 接口的 AnnotationAwareAspectJAutoProxyCreator 在 bean 的后置处理器生效的,也就是说,是在 bean 的生命周期的最后一步生效的。如果每个对象刚刚实例化,就执行了后置处理器,那和 spring 的生命周期是相悖的。为此,spring 采用了三级缓存机制,只有在循环依赖的时候,才会打破生命周期,提前获取代理对象,并且只是最小程度的打破。