Spring循环依赖解决机制
1. 问题定义
什么是循环依赖?
两个或多个Bean相互依赖,形成闭环:
java
@Service
public class UserService {
@Autowired
private OrderService orderService; // UserService依赖OrderService
}
@Service
public class OrderService {
@Autowired
private UserService userService; // OrderService依赖UserService
}
问题本质:A等B,B等A,形成死锁!
2. 解决方案:三级缓存机制
2.1 缓存结构
缓存级别 | 名称 | 存储结构 | 存储内容 | 作用 |
---|---|---|---|---|
🏡 一级缓存 | singletonObjects |
<String beanName, Object bean> |
完整Bean | 存储完全初始化的Bean |
🏘️ 二级缓存 | earlySingletonObjects |
<String beanName, Object earlyBean> |
早期Bean | 存储实例化但未完成属性注入的Bean |
🏭 三级缓存 | singletonFactories |
<String beanName, ObjectFactory<?> factory> |
Bean工厂 | 存储Bean的工厂方法 |
2.2 "早期对象"详解
早期对象 = 实例化后,属性注入前的对象
java
// 早期对象状态
UserService earlyUser = new UserService(); // ✅ 已实例化(有内存地址)
// earlyUser.orderService = null; // ❌ 属性还是null,未注入
// 完整对象状态
UserService completeUser = new UserService(); // ✅ 已实例化
completeUser.setOrderService(orderService); // ✅ 属性已注入
2.3 Bean创建三阶段
阶段 | 操作 | 对象状态 | 存储位置 |
---|---|---|---|
1. 实例化 | new UserService() |
早期对象 | 三级缓存的工厂里 |
2. 属性注入 | user.setOrderService(...) |
正在装修 | 可能在二级缓存 |
3. 初始化完成 | 完整可用 | 完整对象 | 一级缓存 |
3. 解决流程
3.1 流程概述
- 建A毛坯房 :
A a = new UserService()
,ObjectFactory.put("A", () -> a)
- A装修需要B :
a.setOrderService(getBean("B"))
,发现需要OrderService - 建B毛坯房 :
B b = new OrderService()
,ObjectFactory.put("B", () -> b)
- B装修需要A :
b.setUserService(getBean("A"))
,发现需要UserService - 🔑 关键步骤:从三级缓存获取A的工厂,提供A的MVP版本给B
- B装修完成 :
b.setUserService(earlyA)
,B完成创建 - A装修完成 :
a.setOrderService(b)
,A完成创建
3.2 详细代码流程
java
// 1. 建A毛坯房
UserService a = new UserService(); // 毛坯房建好
ObjectFactory.put("userService", () -> a); // 图纸存三级缓存
// 2. A装修需要B
a.setOrderService(getBean("orderService")); // 开始装修,发现需要B
// 3. 建B毛坯房
OrderService b = new OrderService(); // B毛坯房建好
ObjectFactory.put("orderService", () -> b); // B图纸存三级缓存
// 4. B装修需要A - 冲突发生!
b.setUserService(getBean("userService")); // B需要A,但A还没装修完
// 5. 冲突解决:提供A的MVP版本
ObjectFactory factory = get("userService"); // 拿A的图纸
UserService earlyA = factory.getObject(); // 提供MVP版本的A
EarlyCache.put("userService", earlyA); // 缓存MVP版本
// 6. B装修完成
b.setUserService(earlyA); // B拿到A的MVP版本,装修完成
singletonObjects.put("orderService", b); // B存入一级缓存
// 7. A装修完成
a.setOrderService(b); // A拿到完整的B,装修完成
singletonObjects.put("userService", a); // A存入一级缓存
3.3 核心洞察
第5步是解决冲突的关键:通过ObjectFactory提供A的MVP版本,打破死锁
MVP版本A的特点:
- ✅ 可用:有内存地址,可以被引用
- ❌ 不完整:依赖属性还是null,功能不完整
- 🔄 会升级:后续会完成属性注入,变成完整版
- 🎯 关键作用:让B能继续执行,打破循环等待
4. 设计原理
4.1 为什么用ObjectFactory?
问题 :既然已经new UserService()
了,为什么不直接存已经new
好的对象,而要存工厂?
答案:为了支持AOP代理!
java
// 传统做法:存对象
cache.put("A", a); // 直接存对象
// Spring做法:存工厂
factoryCache.put("A", () -> a); // 存"A的工厂"
// 工厂的优势:
ObjectFactory<A> factory = () -> {
if (needProxy) {
return createProxy(a); // 可以返回代理对象
}
return a; // 或者返回原对象
};
AOP场景详解:
- 如果直接存原始对象到二级缓存
- 后来Spring发现需要事务代理
- 但OrderService已经拿到了original的引用,不是proxy
- 结果:OrderService调用userService时没有事务功能
ObjectFactory解决方案:
- 存工厂,延迟决定
- 循环依赖时才调用工厂决定返回什么
- 保证所有引用都指向同一个版本(原始或代理)
4.2 为什么需要三级缓存?
如果只有三级缓存:
java
// 问题:每次都要调用工厂方法
B需要A → factory.getObject() → 创建代理A1
C需要A → factory.getObject() → 创建代理A2
D需要A → factory.getObject() → 创建代理A3
// A1、A2、A3是不同的代理对象!违背单例原则
有了二级缓存:
java
// 解决:缓存工厂生产的对象
第一次需要A → 调用工厂 → 存入二级缓存
后续需要A → 直接从二级缓存获取
--
关键理解
二级缓存存的就是第一步new的A:
java
UserService a = new UserService(); // 第1步创建
ObjectFactory.put("userService", () -> a); // 工厂返回这个a
UserService earlyA = factory.getObject(); // earlyA就是第1步的a
System.out.println(a == earlyA); // true,同一个对象
4.3 三级缓存分工
缓存 | 职责 | 何时使用 |
---|---|---|
🏭 三级 | 存工厂,按需生产 | 循环依赖时第一次需要早期对象 |
🏘️ 二级 | 存早期对象,避免重复生产 | 循环依赖时后续需要同一个早期对象 |
🏡 一级 | 存完整对象,正常使用 | Bean完全创建后的正常获取 |
5. 限制条件
依赖类型 | 是否支持 | 原因 |
---|---|---|
❌ 构造函数循环依赖 | 不支持 | 实例化时就需要依赖,无法提供早期对象 |
✅ setter注入循环依赖 | 支持 | 先实例化,后注入,可以提供早期对象 |
6. 核心要点
- 解决原理:提供MVP版本的早期对象,打破循环等待
- 关键步骤:第5步通过ObjectFactory提供早期对象是核心
- 对象一致性:从始至终都是同一个对象,只是在不同阶段存储在不同缓存
- AOP支持:ObjectFactory可以根据需要返回原对象或代理对象
- 性能优化:二级缓存避免重复调用工厂方法
核心思想:先给MVP版本,后面再完善!