三级缓存
什么是三级缓存
首先要说清楚,spring是做什么用的。spring最核心的功能,控制反转,依赖注入。名词不解释,本人对名词的理解也有限。白话说,就是内置bean容器,为上层应用提供bean的生命周期管理。
也就是大家要写代码时,例如Java中,写一个@Autowired,就可以完成自动注入,不需要自己去new对象。这有什么好处呢?应用里引入redis的时候,只需要写一个redisTemplate就行了。
这是个人的一点肤浅理解,未必正确,各位看官一听就行。
那么spring如何为代码中使用redisTemplate的地方,注入对应的对象呢?这个地方大家应该都可以想明白,创建一个redisTemplate对象,给所有需要注入的地方使用,也就是单例模式。
这个实现大家都可以想到,用一个集合把对象存起来,最好是使用Map
在DefaultSingletonBeanRegistry类中有三个变量,是spring bean容器的三级缓存(一二三的顺序先忽略),就是下面代码片段中的三个map变量:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** Maximum number of suppressed exceptions to preserve. */
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
/** 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);
各位看官看到了吧,这就是三级缓存。
等等等等,存个对象,无非用名字一搜,一个map就够了啊,为什么会有三缓存呢?差点溜过去吧 ^_^ 。我们一点一点来,先不说为什么有三级缓存,先说这个:
为什么会有二级缓存
明明一个map就完事了,为什么还要有二级缓存呢?百度上文章 一大把,说得很明确,为了解决循环依赖。什么是循环依赖?老铁,这个自行百度吧。
这里要说明一点,spring解决不了构造方法参数的循环依赖,A的构造方法里调用了B的方法,B的构造方法里调用了A的方法,谁也解决不了。
能解决的,只是类成员变量(具有set方法)的循环依赖。A里有B,B里有A,并且各自都有set方法。如何解决呢?
spring将创建对象的过程分成了几步(不在本文讨论范围内),大概就是先执行构造方法,完成实例化,然后对类成员进行赋值,完成初始化。
只有彻底完成初始化,成为可用对象时,才放到一级缓存中。
按照这个步骤,当创建A时,先把A放到二级缓存里,发现A依赖B,在缓存里搜索B,没有,那就创建B,创建B的过程中发现依赖A,在缓存里查找,嘿找到了!给B里的成员变量A赋值,
顺利完成B对象的创建并放到一级缓存里,回过头来给A里的成员变量B赋值,顺利完成A对象的创建,圆满成功!
等等等等,就这?这不和其他文章一样么?只说明了二级缓存的作用,没有说明为什么要使用二级缓存,换句话说,没有说明,只用一级缓存,为什么不能解决循环依赖?
好吧,又差点溜过去。
只用一级缓存,解决不了循环依赖么?不是不能解决,而是有弊端,还请列位上眼:
按照这个步骤(上面说的spring分步创建对象的步骤),当创建A时,先把A放到一级缓存里,发现A依赖B,在缓存里搜索B,没有,那就创建B,创建B的过程中发现依赖A,在缓存里查找,嘿找到了!给B里的成员变量A赋值,顺利完成B对象的创建并放到一级缓存里,回过头来给A里的成员变量B赋值,顺利完成A对象的创建。
这一级缓存,不也解决了循环依赖问题了,和刚才的描述有啥差异么?!
有没有差异,列位仔细看,刚才的描述里,可是以“圆满成功”结尾的啊,这里没有,难道它不圆满?还真不圆满,而且列位都能想到:
把A放到一级缓存里了,如果在完成初始化之前,有人来拿走A调用了一下,可能就NPP了啊,咱优秀的程序员可不能这么干啊。
咱得给他补补:
嘿这样,咱给A弄个标记,让来拿对象的人知道,这个对象是不完整的,不能用。那好说啊,一个true/false就解决了。但是不行啊,这个变量放哪呢?嗯把A包装一层,在一级缓存里放一个
Wrapper对象,Wrapper里有一个标记,有一个引用类型,就OK了啊。哦不对,这样会造成所有人来拿对象的时候,都要判断一下。。。太麻烦了,咱优秀的程序员,可不能这么干。
对啊,再弄个二级缓存不就行了,把没创建完成的对象,先放到二级缓存里,直到创建完成了,才放到一级缓存中,二级缓存,就是专门解决循环依赖使用,如果类型A没有发生循环依赖,
那它的创建过程就是:实例化,放到二级缓存,初始化,放到一级缓存,完事。
如果类型A与B发生了循环依赖,那它的创建过程就是:实例化A,放到二级缓存,实例化B,放到二级缓存,初始化B(从二级缓存拿到A的引用),将B放到一级缓存,初始化A,将A放到一级缓存,完事!
其他人该怎么用还是怎么用,完全不影响。这样才符合咱优秀程序员的风格嘛!
就这样,即解决了循环依赖,又照顾了代码简洁,以及性能,这就是二级缓存的诞生之路。
为什么会有三级缓存
先说个结论,三级缓存就是给AOP动态代理准备的,为了编码整洁,为了集成方便,spring为AOP准备了三级缓存,是一种“框架化编程”的结果,是优秀代码。
如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:
不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖`的情况下,Bean就可以按着Spring设计原则的步骤来创建。 Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢? Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map
earlySingletonObjects。 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
没错,Spring bean工厂的动态代理,就是在这个地方,利用这个ObjectFactory实现的。很多文章这样写,spring有两个选择,选择了第二种。
“那选择第二种,就必须使用三级缓存?二级缓存就不行吗?嘿我不是和你杠,我就是想弄明白。”
那我们一起来看一下,三级缓存都是在哪使用到了。通过查找代码,除了几处安全put、remove元素之外,只在一处使用了三级缓存中的元素,见下面方法:
@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;
}
上面代码也很明了,先查找一级缓存,没有就查找二级缓存,还没有,查找三级缓存。我们用二级缓存,能完成这个功能吗?
答案是能。
来重新看三级缓存的定义
//一级
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);
都是Map,虽然三级缓存中不能放二级缓存的元素,但是二级缓存却可以放三级缓存的元素。
OK,替换方案,它不就来了嘛:
删除三级缓存,将所有对三级缓存的put\remove,替换成二级缓存,对应的逻辑进行微调。重点只在一处地方,就是刚才图中的getSingleton方法。
在这个方法里,查找二级缓存时,如果取出来的对象是ObjectFactory类型,那么就需要再调用一次getObject方法
是不是有点熟悉?和FactoryBean类似了。
这个方案,嗯,有点不圆满,如果用户真的需要存一个ObjectFactory对象,咋办呢?可以准备一个Set,把我们自己放进去的ObjectFactory,做个记录嘛,然后拿的时候,比对一下。就解决了啊!是的,这样是可以解决的。而我们引入的这个Set,那不就是三级缓存么?所以,还是为了制造优秀代码,spring引入了三级缓存。
我刚才还多了一嘴,说三级缓存就是为了给AOP准备的。诸位上眼,来看看鄙人收集的证据:
1、哪里对三级缓存进行put操作
只有一个方法,只有一个方法,只有一个方法啊,对三级缓存进行了put操作,就是下面图里的代码:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory); //唯一put的地方
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
顺藤摸瓜,这个方法,又恰好只在一个地方被调用,就是doCreateBean里,看证据:
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
也就是说,完成实例化,立马放入三级缓存。我们再看这个getEarlyBeanReference方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
它调用了一个BeanPostProcessor的getEarlyBeanReference方法,再找这个方法的具体实现:
只有两个类实现了这个方法,Instanxxxxx这个类还是个空方法。那么,可以肯定,这个方法就是给AbstractAutoProxyCreator准备的了:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
再看一下wrapIfNecessary方法:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//这里创建代理对象
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
上面连续的两张图,可以看到,为什么都说三级缓存是给动态代理准备的了。而且,spring的三级缓存,就是给AOP动态代理用的,确认无疑。
总结
spring的三级缓存真的必要吗?通过上面的探讨,可以发现,二级缓存加其他措施,也是可以实现的。然而spring是一个框架,框架更要考虑如何优雅的集成其他组件。
通过我们的分析可以看到,spring仅通过一个接口,就支撑了AOP组件,甚至spring根本不关心,你是要进行动态代理,还是要进行其他操作,spring只是提供了这么一种方式,一个机会,让其他组件可以在bean实例化完成之后,彻底初始化之前,对bean做一些操作。而当前AOP这种操作,恰好就是产生了一个动态代理对象,来替换原对象。所以大家都说,三级缓存,是为了解决动态代理问题。
然而做的开发者,大家一定要理解更深一层的原因,为什么spring要引入三级缓存,是二级缓存真的办不了吗?这样才能加深理解,才能彻底弄明白,有助于大家技术能力的提升。