我们知道Spring为了解决循环依赖,使用了三级缓存,那么什么是Spring的三级缓存,为什么要Spring要使用三级缓存?一级缓存,二级缓存,能不能解决循环依赖的问题?我们带着问题来推理一下这几方案。
循环依赖
在Spring中,存在循环依赖的情况,即两个或多个Bean互相依赖,形成回环,从而进入死循环,最终导致OOM
这里说一下,在prototype模式的Bean和constructor注入的情况时,Spring解决不了循环依赖的问题的,会直接抛出异常,而在singleton模式下的Bean且通过Setter的方式注入,Spring会通过多级缓存来解决循环依赖的问题,那么我们可以顺着这个思路自己来想一下,这个缓存怎么加,加几级缓存,一级?二级?三级?
一级缓存
我们发现,如果要解决循环依赖的问题,其实只要一级缓存就可以了,如下图
通过流程图,我们可以看到,其实只要一级缓存就可以解决循环依赖的问题,但是这里就会有一个问题,那就是缓存中的Bean有可能是已经初始化完成的Bean,也有可能是刚实例化完成而并没有注入属性的不完整的Bean,两种不同状态的Bean参杂在一起,这显然不符合设计规范,缺乏安全性。
二级缓存
上面阐述了一级缓存存在的问题以及设计上的缺陷,那么我们再加一级缓存专门保存未创建完成的Bean,就是二级缓存,如下图
二级缓存确实解决了循环依赖,也清楚的分开了创建完成的Bean和未创建完成的Bean,但是,如果存在AOP的话,就会出现一些问题,如下图
从图中我们发现,我们最终使用的其实是BeanA的代理对象,而BeanB中注入的确是BeanA的原始对象,这显然不符合我们的要求,那么怎么解决?下面提供几种解决方案。
方案一
我们可以在拿到二级缓存的时候直接生成BeanA的代理对象,这样BeanB中注入的就是代理对象了,如下
看图中我标红的地方可以发现,如果在拿二级缓存的时候生成BeanA的代理对象,与最后返回的BeanA的代理对象并不是同一个对象,并不符合单例的要求。
方案二
改变创建代理对象的时机,也就是说实例化Bean以后直接创建Bean的代理对象并放入二级缓存,如下
这种方案看似很好解决了依赖循环和AOP的问题,但是并没有被Spring设计者选择,从网上看原因应该有下:
- 它改变了Spring普通Bean创建代理的时机,破坏了原有的设计,且循环依赖不常发生,如果这样做就本末倒置了。
- 在二级缓存中混杂着代理对象和原始对象,当获取一个对象时根本不知道是代理对象还是普通对象,增加了识别的复杂度,不符合设计规范。
以上是我结合自己分析和网上找到的答案,如果有什么地方不对,可以指正。
终极Spring三级缓存方案三
如流程所示,三级缓存解决了循环依赖和代理的问题,也是Spring所采用的方案,以下就是Spring的三级缓存
一级缓存:singletonObjects,存放经过实例化和初始化的对象。
二级缓存:earlySingletonObjects,存放未创建完成的Bean。
三级缓存:singletonFactories,三级缓存用于存放该Bean的ObjectFactory,当创建一个Bean会先将该Bean包装为ObjectFactory放入三级缓存,其实就是传入的一个lambda表达式
getEarlyBeanReference方法
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;
}
在此处会判断是否实现BeanPostProcessor,如果实现,会为其创建代理对象,从而解决了上述问题。
通过这些例子,可以发现三级缓存确实是一种优雅的解决循环依赖的方式,增加了程序的健壮性和可扩展性,以上是我对Spring三级缓存的理解,如有不对,请大家指正。