Spring如何使用三级缓存解决循环依赖
为什么说Spring使用三级缓存?
这里说的三级缓存指Spring在创建单例 bean 的时候使用三层缓存来解决循环依赖。
直接看代码 org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
:
@Nullable
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<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
核心思路很简单,将创建完成的单例全部放入缓存中,后续再次创建时先从缓存中获取,最终保证同一个类只创建一次。
能否只用一级缓存?
如果只用一级缓存,我们需要在创建 bean 后立刻将其放入缓存中,后续再次加载该 bean 时直接使用缓存中的 bean。
思考一下有没有什么问题?
🤔
🤔
🤔
🤔
🤔
🤔
🤔
🤔
😯:从缓存中获取的 bean 可能并未完成初始化,部分场景下是否会造成空指针等异常?
怎么办❓
引入二级缓存
为了区分开正在初始化与初始化完成的 bean ,我们需要引入二级缓存,新创建的 bean 先放入二级缓存中,待初始化完成后再加入到一级缓存中。
当我们遇到需要拿到 bean 后立刻使用的场景,就只从一级缓存中获取。
什么?还有问题 ?
能否支持 bean 的动态代理?
理一理流程:
- 创建 bean 实例
- 扔入二级缓存中
- 进行初始化,生成 bean 动态代理
- 将代理扔入一级缓存并删除二级缓存
woc!!
二级缓存和一级缓存中的 bean 并不相同,如果存在外部引用已经指向了二级缓存中的 bean 怎么办?
我们知道在动态代理场景下,对 bean 的引用实际应该指向其代理,否则无法使用代理功能。比如 AOP 就需要依赖代理。
使用三级缓存
第三级缓存与其他两级不太相同,第三级存储的是 bean 的创建工厂。
org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
当 bean 实例创建完成后为其生成一个专属工厂实例,并放入三级缓存中。
org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
这里 getEarlyBeanReference
会调用 SmartInstantiationAwareBeanPostProcessor
接口去生成一个 bean 的早期引用,如果 bean 需要被代理,getEarlyBeanReference
将提前为 bean 生成一个代理对象。
在 bean 初始化过程中遇到外部引用,则将 getEarlyBeanReference
生成的代理类扔入二级缓存,后续在 bean 的初始化过程中,将延用该代理对象,不再生成新代理。
通过上述方式引入三级缓存后,满足了动态代理的场景。
简单总结
一级缓存
用于缓存所有初始化完成的单例 bean
二级缓存
缓存创建完实例但未初始化的单例 bean
三级缓存
缓存 ObjectFactory,使用 getEarlyBeanReference
方法在代理场景下根据 bean 提前生成其代理对象,保证需要代理的 bean 在初始化过程中,其代理能正确地被放入二级缓存。
最后,给大家提个醒
在我们自己实现 BeanPostProcessor
去扩展代理时,最好直接实现SmartInstantiationAwareBeanPostProcessor
,并重写 getEarlyBeanReference
方法。