8.6 KiB
8.6 KiB
Spring三级缓存机制学习笔记
📚 概述
Spring三级缓存是Spring框架用来解决单例Bean循环依赖问题的核心机制。它通过三个不同的缓存Map来存储Bean在不同生命周期阶段的状态。
🎯 学习目标
- 理解什么是循环依赖问题
- 掌握三级缓存的结构和作用
- 理解循环依赖的解决流程
- 学会分析相关源码
🔍 核心概念
什么是循环依赖?
当两个或多个Bean相互依赖时形成的依赖环:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB; // A依赖B
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA; // B依赖A
}
问题:如果没有特殊处理,会陷入无限循环创建对象的死循环。
🏗️ 三级缓存结构
Spring使用三个Map来管理Bean的不同状态:
/** 一级缓存:完整的单例Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:早期暴露的Bean对象 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 三级缓存:Bean工厂对象 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
各级缓存详解
| 缓存级别 | 名称 | 作用 | 存储内容 |
|---|---|---|---|
| 一级缓存 | singletonObjects | 成品仓库 | 完全初始化完成的Bean |
| 二级缓存 | earlySingletonObjects | 半成品仓库 | 实例化但未完成依赖注入的Bean |
| 三级缓存 | singletonFactories | 工厂仓库 | Bean的ObjectFactory,支持AOP代理 |
🔄 循环依赖解决流程
以ServiceA ↔ ServiceB循环依赖为例:
流程图
1. 创建ServiceA
↓
2. ServiceA实例化 → 放入三级缓存
↓
3. ServiceA需要ServiceB → 开始创建ServiceB
↓
4. ServiceB实例化 → 放入三级缓存
↓
5. ServiceB需要ServiceA → 从缓存获取ServiceA早期引用
↓
6. ServiceA从三级缓存移至二级缓存
↓
7. ServiceB完成创建 → 放入一级缓存
↓
8. ServiceA完成创建 → 从二级缓存移至一级缓存
详细步骤
第1步:开始创建ServiceA
// 1. 实例化ServiceA(构造函数)
ServiceA serviceA = new ServiceA();
// 2. 将ServiceA的工厂放入三级缓存
singletonFactories.put("serviceA", () -> getEarlyBeanReference("serviceA", serviceA));
// 3. 开始属性注入,发现需要ServiceB
第2步:开始创建ServiceB
// 1. 实例化ServiceB(构造函数)
ServiceB serviceB = new ServiceB();
// 2. 将ServiceB的工厂放入三级缓存
singletonFactories.put("serviceB", () -> getEarlyBeanReference("serviceB", serviceB));
// 3. 开始属性注入,发现需要ServiceA
第3步:获取ServiceA早期引用
// 调用getSingleton("serviceA", true)
Object serviceA = singletonObjects.get("serviceA"); // 一级缓存:null
if (serviceA == null) {
serviceA = earlySingletonObjects.get("serviceA"); // 二级缓存:null
if (serviceA == null) {
ObjectFactory factory = singletonFactories.get("serviceA"); // 三级缓存:找到!
serviceA = factory.getObject(); // 获取早期引用
earlySingletonObjects.put("serviceA", serviceA); // 移到二级缓存
singletonFactories.remove("serviceA"); // 从三级缓存移除
}
}
第4步:完成ServiceB创建
// ServiceB获得ServiceA的早期引用,完成属性注入
serviceB.setServiceA(serviceA);
// ServiceB初始化完成,放入一级缓存
singletonObjects.put("serviceB", serviceB);
第5步:完成ServiceA创建
// ServiceA获得ServiceB的完整引用,完成属性注入
serviceA.setServiceB(serviceB);
// ServiceA初始化完成,从二级缓存移到一级缓存
singletonObjects.put("serviceA", serviceA);
earlySingletonObjects.remove("serviceA");
💡 核心源码分析
getSingleton方法(核心逻辑)
/**
* 从缓存中获取单例Bean
* @param beanName Bean名称
* @param allowEarlyReference 是否允许早期引用
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存获取完整Bean
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存没有,且Bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二级缓存获取早期Bean
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果二级缓存也没有,且允许早期引用
if (singletonObject == null && allowEarlyReference) {
// 三级缓存获取Bean工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂创建早期引用
singletonObject = singletonFactory.getObject();
// 升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
addSingletonFactory方法
/**
* 添加单例工厂到三级缓存
*/
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
// 从二级缓存移除
this.earlySingletonObjects.remove(beanName);
}
}
}
🤔 常见问题
Q1: 为什么需要三级缓存?二级不够吗?
答: 三级缓存主要是为了支持AOP代理:
- 如果Bean需要被代理(如@Transactional),不能直接暴露原始对象
- 通过ObjectFactory可以在需要时创建代理对象
- 保证循环依赖中获取的是正确的代理对象
Q2: 构造器循环依赖能解决吗?
答: 不能!三级缓存只能解决属性注入的循环依赖:
// ❌ 构造器循环依赖 - 无法解决
@Component
public class ServiceA {
public ServiceA(ServiceB serviceB) { }
}
// ✅ 属性注入循环依赖 - 可以解决
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
Q3: Prototype作用域的循环依赖呢?
答: 不能解决!因为:
- Prototype每次都创建新实例
- 没有缓存机制
- 会导致无限递归创建
🎯 实践建议
1. 避免循环依赖的最佳实践
// ❌ 避免:直接的双向依赖
@Service
public class UserService {
@Autowired private OrderService orderService;
}
@Service
public class OrderService {
@Autowired private UserService userService;
}
// ✅ 推荐:引入第三方服务
@Service
public class UserOrderService {
@Autowired private UserService userService;
@Autowired private OrderService orderService;
public void processUserOrder(Long userId, Long orderId) {
// 协调两个服务的交互
}
}
2. 使用@Lazy注解延迟加载
@Service
public class ServiceA {
@Autowired
@Lazy // 延迟加载,避免循环依赖
private ServiceB serviceB;
}
3. 通过ApplicationContext获取Bean
@Service
public class ServiceA implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void someMethod() {
// 运行时获取,避免循环依赖
ServiceB serviceB = applicationContext.getBean(ServiceB.class);
}
}
📝 学习总结
关键点记忆
- 三级缓存目的:解决单例Bean的循环依赖
- 核心思想:提前暴露Bean的早期引用
- 适用范围:仅限于属性注入的循环依赖
- AOP支持:通过ObjectFactory支持代理对象
面试重点
- 能够清晰描述三级缓存的结构和作用
- 能够完整说出循环依赖的解决流程
- 了解哪些情况下循环依赖无法解决
- 知道避免循环依赖的最佳实践
📚 扩展阅读
- Spring官方文档:Bean的生命周期
- 源码位置:
DefaultSingletonBeanRegistry类 - 相关概念:Spring AOP、Bean作用域
- 设计模式:工厂模式、单例模式
创建时间:2025-06-25
最后更新:2025-06-25
难度等级:⭐⭐⭐⭐☆