此页内容

010、Spring 如何处理循环依赖?

747字约2分钟

2025-02-26

Spring 中主要是通过三级缓存来处理循环依赖,这个三级缓存也可以视为 3 个 Map,其中:

  • 一级缓存:存储完整的 Bean
  • 二级缓存:存储早期暴露的 Bean 实例,这些实例已经实例化但尚未初始化(包括属性填充和初始化方法调用)。
  • 三级缓存:存储 Bean 的对象工厂

首先需要了解 Bean 的生命周期,其大致分为以下 4 个步骤:


1、实例化 Bean --> 2、属性注入 --> 3、初始化 Bean --> 4、使用 --> 5、销毁 Bean


其次循环依赖就是:


​ Bean A 依赖 Bean B,Bean B 又依赖 Bean A,创建 Bean A 的时候会先实例化 Bean A也就是new A(),然后在进行属性填充的时候发现 A 依赖 B,那么再去创建 Bean B,先实例化,在其属性注入时发现其依赖 Bean A,从此进入循环。


引入三级缓存后解决循环依赖的思路如下:

  1. 当 Spring 容器创建 Bean A 时,会先将 A 的对象工厂放入三级缓存

  2. 当 A 需要注入 B 时,容器会尝试创建 B。同样地,B 的对象工厂也会被放入三级缓存。

  3. 当 B 需要注入 A 时(循环依赖发生),容器会首先在一级缓存中查找 A,未找到后会去二级缓存查找,再未找到则去三级缓存中通过工厂对象创建 A 的早期引用(此时 A 尚未初始化),然后将这个早期引用放入二级缓存,并从三级缓存中移除工厂对象。

  4. B 完成属性注入和初始化后,会被放入一级缓存。

  5. 回到 A 的创建流程,此时 A 可以从一级缓存中获取到 B 的完全初始化对象进行注入,然后 A 完成初始化并放入一级缓存。

以上便解决了循环依赖。

AOP 的影响

实际上如果没有使用到 AOP(面向切面编程)的话,两层缓存就可以解决循环依赖问题,但是 AOP 是 Spring 一个很重要的特性,不可以不考虑到,像 Bean A 需要用到 AOP 的话,实际上 Bean B 依赖的是 Bean A 的代理对象,存入一级缓存的也只有 Bean A 的代理对象

  • 当使用 AOP 时,创建的 Bean 可能是其代理对象。这意味着即使 B 注入的是 A 的早期引用,这个引用也可能是一个代理对象(如果 A 需要被代理)。
  • 在这种情况下,从三级缓存中通过工厂对象创建的是 A 的代理对象的早期引用,这个引用在初始化阶段会被增强(如添加切面逻辑),最终成为完全初始化的代理对象并放入一级缓存。