这里指的是单例的、非构造依赖的循环引用。很多人都知道Spring用了三层缓存来解决循环依赖,但是不知道其原因,为什么是三级缓存?二级缓存不行吗?一级缓存不可以 ?如果是构造函数中存在循环依赖,则报错。
bean的流程:
beanDefinition->bean实例化->填充属性->aware->beanPostprocessor.before->init->beanPostProcessor.after->单例池+
三级缓存
Spring 解决循环依赖的核心就是提前暴露对象,而提前暴露的对象就是放置于第二级缓存中。缓存的底层都是Map,至于它们属于第几层是由Spring获取数据顺序以及其作用来表现的。
三级缓存的说明:
名称 | 作用 |
---|---|
singletonObjects | 一级缓存,存放初始化完成的Bean。 |
earlySingletonObjects | 二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 初始化(init) 方法。 |
singletonFactories | 三级缓存,存放的是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。 |
第一 先说说第一级缓存singletonObjects
1.先说一级缓存singletonObjects
。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。
2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。
第二 说说第三级缓存singletonFactories
1.上述我们通过一级缓存已经拿到的对象有什么问题?
根本问题就是,我们拿到的是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的,再比如我们经常使用的配置类注解@Configuration
在缺省情况下(full mode),其内的所有@Bean都是处于动态代理模式,除非手动指定proxyBeanMethods = false
将配置转成简略模式(lite mode)。
2.所以,Spring在bean实例化后,将原始bean放入第三级缓存singletonFactories中,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现。
3.这里还涉及到动态代理的提前,用earlyProxyReferences这个map记录被提前动态代理的bean。
# 函数签名
addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
# 具体实现由回调决定
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
复制代码
那么如果有动态代理的需求,里面可以埋点进行处理,将原始bean包装后返回。
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;
}
复制代码
这里的getEarlyBeanReference方法就是得到代理对象。
AbstractAutoProxyCreatorl类中getEarlyBeanReference的方法
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
会有一个earlyProxyReference缓存来保存代理对象,如果循环代理中有AOP则将原始bean对象存入earlyProxyReference,用于之后的比较,判断这个bean的AOP是不是提前做过,然后通过**wrapIfNecessary(bean,beanName,cacheKay)**生成代理对象。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
在bean初始化之后,会回调AbstractAutoProxyCreatorl类中的postProcessAfterInitialization方法,如果从earlyProxyReference中获取的对象是原始bean,则说明动态代理还没进行过,表示这是在正常逻辑中的AOP,没有循环依赖的过程,则表示AOP没有提前过,通过**wrapIfNecessary(bean,beanName,cacheKay)**生成代理对象,反之,标明AOP过程已经提前。
3.通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。
第三 说说第二级缓存earlySingletonObjects
1.为什么需要earlySingletonObjects
这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories
这个缓存,即可实现代理又可以缓存数据。
2.从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。
3、再次说说三级缓存的划分及其作用。
一级缓存singletonObjects
是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects
是不完整的bean,没有完成初始化,它与singletonObjects
的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories
,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。