关注公众号 不爱总结的麦穗 将不定期推送技术好文
在 Spring 框架中,三级缓存主要应用于解决 循环依赖 问题,特别是在 Spring IoC
容器初始化时创建 Bean 的过程中。三级缓存可以确保在复杂的 Bean 依赖情况下,Spring 可以通过懒加载和代理的方式避免循环依赖。
什么是循环依赖?
在 Spring 框架中循环依赖是指两个或两个以上的bean互相引用对方,导致它们无法独立创建或初始化。
某些业务在实现的时候可能就是需要bean A依赖bean B,同时bean B又依赖bean A,就形成了循环依赖。Spring存在循环依赖并不会直接报错,只有Spring无法自动解决的时候才会报错。
Spring 如何检测循环依赖
Spring 使用一个singletonCurrentlyInCreation
的Set<String>
集合来跟踪当前正在创建的单例 Bean,它记录了哪些 Bean 当前正在创建过程中。
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
-
当 Spring 开始创建一个 Bean(例如
A
),它会将A
的名字添加到singletonCurrentlyInCreation
集合中。 -
当需要注入其他 Bean(例如
B
)时,Spring 会检查singletonCurrentlyInCreation
集合,如果发现B
的名字已经在集合中,则意味着已经在创建B
,从而发现循环依赖。
Spring 三级缓存
循环依赖检测到后,Spring 会通过三级缓存机制来解决(所谓三级缓存就是三个Map)。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
singletonObjects
,一级缓存,存放所有创建好了的单例BeanearlySingletonObjects
二级缓存,存放所有完成实例化但还未进行属性注入及初始化的BeansingletonFactories
,三级缓存,存放单例Bean的工厂
三级缓存原理
Spring容器的启动是从refresh()方法开始的,在refresh()方法中会调用finishBeanFactoryInitialization()方法,该方法会创建出所有非懒加载的bean。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 实例化Bean
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
// 部分代码省略...
// 实例化的bean(还没初始化的类(当bean为单例 && 容器配置允许循环依赖 && bean正在创建))包装为ObjectFactory对象,放入到三级缓存singletonFactories中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 放入到三级缓存singletonFactories中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// populateBean方法用来装配bean(依赖注入),当发现依赖关系之后会去容器中查找依赖的bean
populateBean(beanName, mbd, instanceWrapper);
// bean功能的增强(aware接口、bean后置处理器、初始化方法),AOP的代理对象在这里生成
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
// 从缓存中获取bean,这里注意第二参数是false
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 部分代码省略 ...
}
}
return exposedObject;
}
再来看看getSingleton()方法是怎样从缓存中获取bean?
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) {
// 当allowEarlyReference为true时从三级缓存中查找
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 从三级缓存中找到之后会移入到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
到这里,spring 如何解决循环引用的,答案就揭晓了:
当AB存在相互依赖时,Spring首先创建A,实例化完成之后把A包装为ObjectFactory对象放入到三级缓存singletonFactories中。然后对A进行依赖注入,此时发现A依赖B,spring会去容器中查找B,发现B还没有创建,所以先去创建B,同样实例化结束之后将B包装为ObjectFactory对象放入到三级缓存中;然后对B进行依赖注入,发现B依赖A,spring会去容器中查找A,通过getSingleton()方法从缓存中查找,会在第三级缓存中找到A的ObjectFactory对象,通过ObjectFactory对象调用getObject方法获取到A,并将A放入到二级缓存earlySingletonObjects中,顺便在三级缓存中把ObjectFactory对象移除。尽管A还没装配结束,但B已经获取到了A,B的初始化完成了,B从三级缓存移入到一级缓存。然后回过头来继续初始化A,将B注入A,A也初始化完成,A从二级缓存移入到一级缓存。
乍一看,好像不需要三级缓存就能解决循环依赖的问题了,两级完全可以了。为什么会需要三级缓存呢?我们别忘了Spring还有一个很重要的特性彻底搞懂Spring AOP。
还是上面AB相互依赖的例子,假设我们对A进行了AOP,Spring会怎么解决呢?
还是之前的场景,当B创建时,发现其依赖于A,但是B需要的是A的代理对象。B会在第三级缓存中找到A的ObjectFactory对象,通过ObjectFactory对象调用getObject方法获取到A的代理对象,并将A的代理对象放入到二级缓存earlySingletonObjects中。
三级缓存存放的是ObjectFactory对象。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
getEarlyBeanReference()方法会判断当前bean是否需要进行AOP处理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 判断当前bean是否需要进行AOP处理
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
// earlyProxyReferences value值是原来的bean
this.earlyProxyReferences.put(cacheKey, bean);
// 生成代理对象
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
当B的初始化完成,回过头来继续初始化A,将B注入A后。A执行到initializeBean()方法时,因为A进行了AOP会去生成代理对象,但是呢A的代理对象已经生成过了,不能再生成一个代理对象。
// populateBean方法用来装配bean(依赖注入),当发现依赖关系之后会去容器中查找依赖的bean
populateBean(beanName, mbd, instanceWrapper);
// bean功能的增强(aware接口、bean后置处理器、初始化方法),AOP的代理对象在这里生成
exposedObject = initializeBean(beanName, exposedObject, mbd);
A的代理对象还是要从缓存中获取。此时一级缓存中还没有,三级缓存中存储的仍然是原本的bean,所以只能从二级缓存中来获取到代理后的bean。这就是需要三级缓存的原因。
if (earlySingletonExposure) {
// 从缓存中获取bean,这里注意第二参数是false
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 部分代码省略 ...
}
}
总结
Spring 中实现依赖注入的常见方式有以下 3 种:
- 属性注入(Field Injection)
- Setter 注入(Setter Injection)
- 构造方法注入(Constructor Injection)
Spring可以自动解决一部分循环依赖的情况:
依赖场景 | 注入方式 | 是否自动解决 |
---|---|---|
AB相互依赖 | 均采用setter方法注入 | 是 |
AB相互依赖 | 均采用属性注入 | 是 |
AB相互依赖 | 均采用构造器注入 | 否 |
AB相互依赖 | A中注入B的方式为setter方法,B中注入A的方式为构造器 | 是 |
AB相互依赖 | B中注入A的方式为setter方法,A中注入B的方式为构造器 | 否 |
(大家可以思考一下为什么?想通了,那就恭喜你)