跳到主要内容

SpringBoot三级缓存

· 阅读需 7 分钟

文字链接:https://www.zhihu.com/question/445446018/answer/3796620273

三级缓存介绍

SpringBoot中的三级缓存主要是为了解决循环依赖问题的。

  • 一级缓存(singletonObjects): 存放的是已经完全实例化和初始化的 Bean,这个 Bean 是完整的,可以随时拿出来直接用,类似成品。比如 Map<String, Object> singletonObjects就是所有生成完成的 Bean 放在这里面。
  • 二级缓存(earlySingletonObjects) 存放的是还没经过 AOP 代理的 Bean,这些 Bean 是一种“半成品”。在处理一些复杂依赖时,Spring 可能会先创建一个“半成品 Bean”防止被人重复创建,结果把引用给弄乱了。比如 Map<String, Object> earlySingletonObjects
  • 三级缓存(singletonFactories) 这是关键。三级缓存里放的是一个 ObjectFactory 工厂引用,也就是生成 Bean 的回调方法。在循环依赖时,这个工厂能够在还没完全生成 Bean 时,就提供一个“临时引用”给其他对象依赖,保证 Bean 的生成不会出错。
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry片段:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
private final Set<String> registeredSingletons = new LinkedHashSet(256);
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16));
...

搞清楚了三级缓存分别是啥,那为啥两级不行?关键就在于循环依赖

举个例子:A 依赖 B,B 也依赖 A。如果没有三级缓存,Spring 必须得等 A 完全生成后才能去创建 B,而 B 也在等 A。这样就成了死循环。即便用二级缓存也不行,因为二级缓存存的“半成品”可能会被误用,导致生成的对象不完整。这样最终拿到的 Bean 就成了残次品。

三级缓存是怎么解决的呢?当 A 依赖 B 时,Spring 会先尝试生成 A,然后把 A 的“工厂引用”放到三级缓存里。这时 B 需要 A 时,就会从三级缓存中拿到这个“工厂引用”生成一个“未完成的 A”。这个“未完成的 A”并不是个半成品,而是个动态代理引用,等 A 完成生成后,这个引用会指向最终的 A 实例。

来看获取bean的方法,如何从缓存中获取:(核心)

allowEarlyReference参数意为是否允许循环依赖,true则从三级缓存获取bean。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}

return singletonObject;
}

这段逻辑很直白,意思就是:先看一级缓存有没有完全生成的对象,如果没有再去二级缓存找半成品,二级缓存也没有,就去三级缓存用工厂生成一个“临时对象”,然后放到二级缓存里,再移除三级缓存

三级缓存真正的作用就是:在生成完成之前就提供一个“可用的引用”,保证其他对象在需要它时不会出错。这就好像提前给了个占位符,让其他对象可以先用着,等生成完成后再替换成真正的 Bean。

很多人提到 AOP 关系,其实就是在这个过程中,Spring 要给 Bean 加上 AOP 代理时,必须等到 Bean 完全生成完成之后。如果没有三级缓存,可能你用的是没代理的“裸”对象,等生成完后再想改就来不及了。三级缓存提供的 ObjectFactory 允许在生成代理时动态切换引用,保证最终拿到的是正确的代理对象。

这里 getSingleton 方法会根据不同的缓存层级去拿 Bean,并且 allowEarlyReference 决定是否从三级缓存生成对象。如果没有三级缓存,遇到循环依赖时,Spring 就会直接炸掉,抛出各种依赖相关的异常。

总结一下:三级缓存就是 Spring 用来解决循环依赖的“最后防线”,它提供一个工厂引用,让其他依赖对象可以先用上动态代理,等生成完成再切换成最终版本。这是两级缓存永远做不到的。

所以,为什么不用两级?简单讲:两级缓存根本搞不定!循环依赖和 AOP 混合场景下,两级缓存分分钟给你整死。三级缓存就像是个灵活的占位符,可以动态处理各种复杂情况。用两级的话,根本扛不住。

在编码时应尽量避免循环依赖!

编码时不建议使用依赖循环,SpringBoot2.6.0已经默认不允许依赖循环了。而且构造器注入bean的方式,是有可能出现依赖循环的。

可以认为解决依赖循环就不太合理。

推荐看视频讲解

【彻底拿捏Spring循环依赖以及三个级别缓存】https://www.bilibili.com/video/BV1HwkvYmEXv