文章目录
- 一、三级缓存的核心组成
- 二、为什么二级缓存不够?
- 1. 初步设想:二级缓存方案
- 2. AOP代理问题的引入
- 3. 三级缓存如何解决这个问题
- 三、三级缓存的工作流程详解
- 四、为什么不能只用二级缓存?
- 五、性能考量
- 六、源码佐证
- 七、总结
Spring解决循环依赖的三级缓存机制是一个精妙的设计,很多开发者会有疑问:为什么需要三级缓存?二级缓存看起来也能解决问题啊?让我们深入分析这个问题。
一、三级缓存的核心组成
首先回顾一下Spring的三级缓存结构:
- 一级缓存(singletonObjects):存放完全初始化好的Bean
- 二级缓存(earlySingletonObjects):存放原始的Bean对象(尚未填充属性)
- 三级缓存(singletonFactories):存放Bean的工厂对象(ObjectFactory)
二、为什么二级缓存不够?
1. 初步设想:二级缓存方案
假设我们只有二级缓存,处理流程可能是这样的:
- 创建Bean A时,实例化后直接放入二级缓存
- 填充属性时发现需要Bean B
- 创建Bean B时,实例化后放入二级缓存
- Bean B需要注入Bean A,从二级缓存中获取
- 完成Bean B的初始化
- 完成Bean A的初始化
看起来似乎也能工作,那为什么Spring不这样做呢?
2. AOP代理问题的引入
关键问题在于Spring AOP代理。如果Bean需要被代理(如使用了@Transactional等AOP增强),简单的二级缓存方案就无法正确处理了。
考虑以下场景:
@Service
@Transactional
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Transactional
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
在这种情况下:
- 如果直接将原始对象放入二级缓存,后续AOP无法介入
- 如果直接将代理对象放入二级缓存,此时Bean还未初始化完成,代理可能不完整
3. 三级缓存如何解决这个问题
三级缓存存储的是ObjectFactory,它可以在需要时决定返回原始对象还是代理对象:
// AbstractAutowireCapableBeanFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这段代码允许后处理器(如AOP代理创建器)在需要时返回代理对象。
三、三级缓存的工作流程详解
让我们通过一个需要AOP代理的例子,看看三级缓存如何工作:
-
创建Bean A
- 实例化A对象(原始对象)
- 将A的ObjectFactory放入三级缓存
singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
-
填充A的属性,发现需要Bean B
- 开始创建Bean B
-
创建Bean B
- 实例化B对象(原始对象)
- 将B的ObjectFactory放入三级缓存
- 填充B的属性,发现需要Bean A
-
解决Bean A的依赖
- 从三级缓存获取ObjectFactory
- 调用getEarlyBeanReference()方法
- 如果A需要代理,这里会返回代理对象
- 否则返回原始对象
- 将结果放入二级缓存,并从三级缓存移除
-
完成Bean B的初始化
- 将初始化好的B放入一级缓存
-
完成Bean A的初始化
- 如果A被代理了,这里要确保返回的是代理对象
- 将最终结果放入一级缓存
四、为什么不能只用二级缓存?
如果只用二级缓存:
- 无法处理AOP代理:要么过早创建代理(可能不完整),要么无法创建代理
- 一致性难以保证:在循环依赖解决过程中,可能得到不一致的Bean引用(原始对象或代理对象混用)
- 扩展性受限:无法灵活应对各种BeanPostProcessor的处理需求
五、性能考量
三级缓存看起来增加了复杂度,但实际上:
- 内存占用小:ObjectFactory是轻量级的
- 按需创建:只有发生循环依赖时才会调用ObjectFactory
- 生命周期清晰:Bean在不同阶段明确处于不同缓存中
六、源码佐证
在DefaultSingletonBeanRegistry
中可以看到明确的处理逻辑:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先检查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 然后检查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 最后从三级缓存获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这段代码清晰地展示了三级缓存的查询顺序和处理逻辑。
七、总结
Spring采用三级缓存而非二级缓存的主要原因包括:
- 支持AOP代理:三级缓存通过ObjectFactory延迟决定返回原始对象还是代理对象
- 生命周期管理:明确区分不同阶段的Bean(原始对象、早期引用、完整Bean)
- 扩展性:允许各种BeanPostProcessor介入Bean的创建过程
- 一致性保证:确保在整个依赖注入过程中Bean引用的一致性
因此,三级缓存不是过度设计,而是Spring为了解决包括AOP代理在内的复杂循环依赖场景所必需的设计。这种机制在保证功能完整性的同时,也兼顾了性能和扩展性。
发布者:admin,转转请注明出处:http://www.yc00.com/web/1754940651a5218088.html
评论列表(0条)