Spring如何解决循环依赖
Spring 框架通过 三级缓存 的机制,巧妙地解决了 循环依赖 的问题。以下是详细的解释和实现原理:
1. 什么是循环依赖?
循环依赖指的是两个或多个 Bean 相互依赖,形成闭环,例如:
1 |
|
在上述代码中,A
依赖 B
,同时 B
又依赖 A
,形成了循环依赖。
2. Spring 如何解决循环依赖?
Spring 通过 三级缓存 的机制,分阶段处理 Bean 的创建和依赖注入,从而解决循环依赖问题。
三级缓存的定义:
一级缓存(Singleton Objects):
- 存放完全初始化好的 Bean。
- 结构:
Map<String, Object>
。
二级缓存(Early Singleton Objects):
- 存放早期暴露的 Bean(未完全初始化)。
- 结构:
Map<String, Object>
。
三级缓存(Singleton Factories):
- 存放 Bean 的工厂对象,用于创建早期暴露的 Bean。
- 结构:
Map<String, ObjectFactory<?>>
。
解决循环依赖的核心思想:
- 在 Bean 实例化后,将其暴露(存入三级缓存),从而允许其他 Bean 提前引用。
- 在 Bean 完全初始化之前,通过早期暴露的引用解决循环依赖问题。
3. 具体过程
以下以 A
和 B
的循环依赖为例,说明 Spring 的处理流程:
(1)创建 Bean A
- 实例化 A:
- 调用 A 的构造函数,创建一个未初始化的 A 对象。
- 暴露 A:
- 将 A 的工厂对象存入三级缓存(
Singleton Factories
)。
- 将 A 的工厂对象存入三级缓存(
- 注入 A 的依赖:
- Spring 发现 A 依赖 B,开始创建 B。
(2)创建 Bean B
- 实例化 B:
- 调用 B 的构造函数,创建一个未初始化的 B 对象。
- 暴露 B:
- 将 B 的工厂对象存入三级缓存(
Singleton Factories
)。
- 将 B 的工厂对象存入三级缓存(
- 注入 B 的依赖:
- Spring 发现 B 依赖 A,尝试从缓存中获取 A。
(3)解决依赖
- 获取 A:
- 从三级缓存中获取 A 的工厂对象,调用
getObject()
方法,生成早期暴露的 A 对象。 - 将早期暴露的 A 对象存入二级缓存(
Early Singleton Objects
)。
- 从三级缓存中获取 A 的工厂对象,调用
- 注入 A 到 B:
- 将早期暴露的 A 对象注入到 B 中。
- 完成 B 的初始化:
- 初始化 B 的其他属性,将完全初始化的 B 对象存入一级缓存(
Singleton Objects
)。
- 初始化 B 的其他属性,将完全初始化的 B 对象存入一级缓存(
- 注入 B 到 A:
- 将完全初始化的 B 对象注入到 A 中。
- 完成 A 的初始化:
- 初始化 A 的其他属性,将完全初始化的 A 对象存入一级缓存(
Singleton Objects
)。
- 初始化 A 的其他属性,将完全初始化的 A 对象存入一级缓存(
4. 三级缓存的作用
- 三级缓存(Singleton Factories):
用于解决循环依赖的关键。它通过工厂对象生成早期暴露的 Bean,避免在 Bean 未初始化时直接返回未完成的实例。 - 二级缓存(Early Singleton Objects):
用于存储早期暴露的 Bean,避免多次生成重复的对象。 - 一级缓存(Singleton Objects):
用于存储完全初始化的 Bean,提供最终的单例对象。
5. 代码模拟
以下是 Spring 解决循环依赖的简化模拟代码:
1 | // 三级缓存 |
6. 限制条件
Spring 仅能解决以下情况的循环依赖:
- 单例 Bean: 仅支持单例作用域的 Bean。
- 构造器注入: 不支持通过构造器注入导致的循环依赖。
7. 总结
Spring 通过三级缓存机制,巧妙地解决了循环依赖问题。其核心思想是 提前暴露未完全初始化的 Bean 引用,从而打破循环依赖的闭环。理解这一机制,有助于更好地设计和调试 Spring 应用程序。