Spring循环依赖

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 流程概述

  1. 建A毛坯房A a = new UserService()ObjectFactory.put("A", () -> a)
  2. A装修需要Ba.setOrderService(getBean("B")),发现需要OrderService
  3. 建B毛坯房B b = new OrderService()ObjectFactory.put("B", () -> b)
  4. B装修需要Ab.setUserService(getBean("A")),发现需要UserService
  5. 🔑 关键步骤:从三级缓存获取A的工厂,提供A的MVP版本给B
  6. B装修完成b.setUserService(earlyA),B完成创建
  7. 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. 核心要点

  1. 解决原理:提供MVP版本的早期对象,打破循环等待
  2. 关键步骤:第5步通过ObjectFactory提供早期对象是核心
  3. 对象一致性:从始至终都是同一个对象,只是在不同阶段存储在不同缓存
  4. AOP支持:ObjectFactory可以根据需要返回原对象或代理对象
  5. 性能优化:二级缓存避免重复调用工厂方法

核心思想:先给MVP版本,后面再完善!

相关推荐
oak隔壁找我4 小时前
Spring Bean 配置详解
后端
aloha_4 小时前
es离线部署与配置
后端
我是天龙_绍4 小时前
用SpringMvc,实现,增删改查,api接口
后端
小蜗牛编程实录5 小时前
MAT分析内存溢出- ShardingSphere JDBC的缓存泄露问题
后端
用户68545375977695 小时前
🚀 Transformer:让AI变聪明的"读心术大师" | 从小白到入门的爆笑之旅
人工智能·后端
深圳蔓延科技5 小时前
SpringSecurity中如何接入单点登录
后端
刻意思考5 小时前
服务端和客户端之间接口耗时的差别
后端·程序员
该用户已不存在5 小时前
Python项目的5种枚举骚操作
后端·python
zjjuejin5 小时前
Maven 云原生时代面临的八大挑战
java·后端·maven