Spring如何解决循环依赖

Spring 框架通过 三级缓存 的机制,巧妙地解决了 循环依赖 的问题。以下是详细的解释和实现原理:


1. 什么是循环依赖?

循环依赖指的是两个或多个 Bean 相互依赖,形成闭环,例如:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class A {
@Autowired
private B b;
}

@Service
public class B {
@Autowired
private A a;
}

在上述代码中,A 依赖 B,同时 B 又依赖 A,形成了循环依赖。


2. Spring 如何解决循环依赖?

Spring 通过 三级缓存 的机制,分阶段处理 Bean 的创建和依赖注入,从而解决循环依赖问题。

三级缓存的定义:

  1. 一级缓存(Singleton Objects):

    • 存放完全初始化好的 Bean。
    • 结构:Map<String, Object>
  2. 二级缓存(Early Singleton Objects):

    • 存放早期暴露的 Bean(未完全初始化)。
    • 结构:Map<String, Object>
  3. 三级缓存(Singleton Factories):

    • 存放 Bean 的工厂对象,用于创建早期暴露的 Bean。
    • 结构:Map<String, ObjectFactory<?>>

解决循环依赖的核心思想:

  • 在 Bean 实例化后,将其暴露(存入三级缓存),从而允许其他 Bean 提前引用。
  • 在 Bean 完全初始化之前,通过早期暴露的引用解决循环依赖问题。

3. 具体过程

以下以 AB 的循环依赖为例,说明 Spring 的处理流程:

(1)创建 Bean A

  1. 实例化 A:
    • 调用 A 的构造函数,创建一个未初始化的 A 对象。
  2. 暴露 A:
    • 将 A 的工厂对象存入三级缓存(Singleton Factories)。
  3. 注入 A 的依赖:
    • Spring 发现 A 依赖 B,开始创建 B。

(2)创建 Bean B

  1. 实例化 B:
    • 调用 B 的构造函数,创建一个未初始化的 B 对象。
  2. 暴露 B:
    • 将 B 的工厂对象存入三级缓存(Singleton Factories)。
  3. 注入 B 的依赖:
    • Spring 发现 B 依赖 A,尝试从缓存中获取 A。

(3)解决依赖

  1. 获取 A:
    • 从三级缓存中获取 A 的工厂对象,调用 getObject() 方法,生成早期暴露的 A 对象。
    • 将早期暴露的 A 对象存入二级缓存(Early Singleton Objects)。
  2. 注入 A 到 B:
    • 将早期暴露的 A 对象注入到 B 中。
  3. 完成 B 的初始化:
    • 初始化 B 的其他属性,将完全初始化的 B 对象存入一级缓存(Singleton Objects)。
  4. 注入 B 到 A:
    • 将完全初始化的 B 对象注入到 A 中。
  5. 完成 A 的初始化:
    • 初始化 A 的其他属性,将完全初始化的 A 对象存入一级缓存(Singleton Objects)。

4. 三级缓存的作用

  • 三级缓存(Singleton Factories):
    用于解决循环依赖的关键。它通过工厂对象生成早期暴露的 Bean,避免在 Bean 未初始化时直接返回未完成的实例。
  • 二级缓存(Early Singleton Objects):
    用于存储早期暴露的 Bean,避免多次生成重复的对象。
  • 一级缓存(Singleton Objects):
    用于存储完全初始化的 Bean,提供最终的单例对象。

5. 代码模拟

以下是 Spring 解决循环依赖的简化模拟代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 三级缓存
Map<String, Object> singletonObjects = new HashMap<>(); // 一级缓存
Map<String, Object> earlySingletonObjects = new HashMap<>(); // 二级缓存
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(); // 三级缓存

// 创建 Bean A
Object a = createBean("A");
singletonFactories.put("A", () -> a); // 暴露 A
Object b = createBean("B"); // A 依赖 B
singletonFactories.put("B", () -> b); // 暴露 B
Object earlyA = singletonFactories.get("A").getObject(); // 获取 A 的早期引用
earlySingletonObjects.put("A", earlyA); // 存入二级缓存
setField(b, "a", earlyA); // 将 A 注入 B
singletonObjects.put("B", b); // B 初始化完成,存入一级缓存
setField(a, "b", b); // 将 B 注入 A
singletonObjects.put("A", a); // A 初始化完成,存入一级缓存

6. 限制条件

Spring 仅能解决以下情况的循环依赖:

  • 单例 Bean: 仅支持单例作用域的 Bean。
  • 构造器注入: 不支持通过构造器注入导致的循环依赖。

7. 总结

Spring 通过三级缓存机制,巧妙地解决了循环依赖问题。其核心思想是 提前暴露未完全初始化的 Bean 引用,从而打破循环依赖的闭环。理解这一机制,有助于更好地设计和调试 Spring 应用程序。