7.Spring循环依赖-三级缓存
Spring循环依赖
-
循环依赖是什么:
伪代码:
// A依赖了B class A{ public B b; } // B依赖了A class B{ public A a; }
如果在Java中这样实现是没有问题的,但是在Spring中,这样就存在问题,因为Spring中的Bean对象并不是简单的new出来的,而是经过一系列的bean的生命周期创建出来的,所以在A还未创建时,B使用A时就会因为找不到A对象而报错,同样也可能在A创建后使用B的时候,B还没有创建的情况。
-
简单来说一下bean的生命周期:
- 扫描class文件创建对应BeanDefinition
- 根据BeanDefinition创建bean
- 首先根据class文件推断构造方法
- 确定构造方法后,反射得到一个对象bean对象(原始对象)
- 接着对bean对象的属性填充,以及其他属性的填充
- 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
- 将最终生成的对象放入单例池中,下次getBean中直接从单例池中拿
可以看到一个Bean创建是经历了很多步骤的,比如Aware回调,初始化等。而上面的代码放在spring中就可以这样解释:
ABean创建-》发现依赖BBean-》触发BBean生命周期创建-》BBean发现依赖ABean-》而ABean正在创建中;
形成了循环依赖,导致ABean、BBean都无法创建。
这就是spring中循环依赖的场景。
spring中提供了解决循环依赖的机制-》三级缓存
1. 三级缓存演变思路
放几张周瑜大都督的图:
循环依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bkBtMrse-1609374445190)(F:\MyFile\学习笔记\Java学习笔记\6.Spring框架\5.Spring原理\5.图片素材\23.循环依赖.png)]
打破这个循环依赖,加入中间人(缓存)
1. 第一级缓存
- 单例池:singletonObjects, Map<beanName,Object>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTsxEO2V-1609374445194)(F:\MyFile\学习笔记\Java学习笔记\6.Spring框架\5.Spring原理\5.图片素材\23.循环依赖2.png)]
A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。
因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。
2. 第二级缓存earlySingletonObjects
用课程中的案例说明:
AService的生命周期
1. 实例化AService(new AService),AService原始对象-》将对象放入Map中,该对象为原始对象AService,各属性未赋值
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池
BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》到Map中去找-》找到AService原始对象赋值给BService
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池
上述一级缓存会产生一个问题放入Map中的对象是一个原始对象,属性未赋值,那么此时我们是将原始对象放入单例池中,还是将AService代理对象放入单例池中呢?
应该放入代理对象,因为在AOP中生成的代理对象属性已经赋值,而不是空值,如果仍然放入原始对象,那么AOP的赋值有什么用嘞。于是问题就又产生了,在BService中的第二步就已经将AService的原始对象赋值给了BService,但是我们在AService中的第4步中将AService的代理对象放入了单例池中,上下不统一,而我们想要的效果是AService的代理对象放入单例池中,同时赋值给BService中的第二步
解决办法:将AOP提前到第一步
- 实例化AService(new AService),AService原始对象-》AOP-》AService代理对象-》放入Map中,AService代理对象
- 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
- 填充其他属性
- 初始化后(AOP)-》AService的代理对象(此时第四步就不需要了)
- 添加到单例池
第一步进行AOP的场景:AService产生循环依赖的时候
此时又带来一个问题:Spring如何判断AOP是否已经执行过?
在AService中是无法判断的,所以我们将判断放入BService的第2步中:
填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》AService正在创建中(就认为AService出现了循环依赖)-》到Map中去找-》找到AService原始对象赋值给BService。
于是我们可以在AService的第0步中,将正在创建的AService放入createingSet中,该集合中存放的就是正在创建的bean对象
于是BService的第二步变成了如下:
填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》createingSet-》表示AService出现了循环依赖-》AOP-》AService代理对象赋值给BService。
于是这里又产生了新的问题:
BService中产生的代理对象可以直接放入单例池中吗?是不可以的,因为在这里AService的代理对象的属性是不完整的,那么什么时候应该将BService中AService产生的代理对象放入单例池中呢?而且假设有CService依赖AService,导致AService还需要填充CService属性,于是产生了和BService一样的过程,这就导致了有创建了AService的代理对象
那怎么解决呢?缓存喽,即BService第二步不要直接将AService的代理对象放入单例池中,而是放入earlySingletonObjects的集合中,即二级缓存中
填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》createingSet-》表示AService出现了循环依赖-》AOP-》AService代理对象赋值给BService-》将AService的代理对象放入二级缓存earlySingletonObjects<AService:AService代理对象>
二级缓存就解决了这么多的问题。最终要的是保证了AService的代理对象的唯一。
整个二级缓存的过程如下
AService的生命周期
1. 实例化AService(new AService),AService原始对象-》将对象放入Map中,该对象为原始对象AService,各属性未赋值
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
4.5. 从二级缓存中取出AService的代理对象
5. 添加到单例池(从二级缓存中取出对象放入一级缓存)
BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》代表AService出现了循环依赖-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池
经过上面两个缓存还存在的问题就是:在AService的第四步中,怎么判断AService已经进行过AOP了呢?另一个问题:在BService第2步中AOP要拿到AService的原始对象进行代理,那么是如何拿到的呢?
3. singletonFactories:
这就用到了三级缓存singletonFactories:
BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》代表AService出现了循环依赖-》第三级缓存-》AService原始对象-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池
2. 源码分析
doCreateBean方法中:
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// 如果当前创建的是单例bean,并且允许循环依赖,并且还在创建过程中,那么则提早暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 此时的bean还没有完成属性注入,是一个非常简单的对象
// 构造一个对象工厂添加到addSingletonFactory中
// 第四次调用后置处理器,传入一个lambda作为参数
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
// 将对象暴露出去
Object exposedObject = bean;
所以上述过程可以改变写法
AService的生命周期
1. 实例化AService(new AService),AService原始对象-》第三级缓存将对象放入Map<AService:lambda(AService原始对象,beanName,BeanDefinition)>
2. 填充BService属性-》从单例池中去找BService对应的Bean-》找不到-》创建BService对应的Bean
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
4.5. 从二级缓存中取出AService的代理对象
5. 添加到单例池(从二级缓存中取出对象放入一级缓存)
BService
1. 实例化BService(new BService),BService原始对象-》将对象放入Map中,该对象为原始对象BService,各属性未赋值
2. 填充BService属性-》从单例池中去找AService对应的Bean-》找不到-》二级缓存-》找不到-》createing-》第三级缓存将对象放入Map<AService:lambda(AService原始对象,beanName,BeanDefinition)>-》执行lambda表达式-》AOP-》AService代理对象赋值给BService-》将代理对象放入二级缓存<AService:AService代理对象>
3. 填充其他属性
4. 初始化后(AOP)-》AService的代理对象
5. 添加到单例池
lambda表达式
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(exposedObject, beanName);
// AService出现了循环依赖
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 判断
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey); // 代理对象,执行AOP
}
解释源码中判断和原始bean相等的意义:
// 4. 初始化和BeanPostProcessor
exposedObject = initializeBean(beanName, exposedObject, mbd);
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 如果提前暴露的对象和经过完整的生命周期后的对象相等,则把代理对象赋值给exposedObject
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
这里对exposedObject作判断经过BeanPostProcessor是否和bean相等,这里这个bean是一个原始对象,如果在经过BeanPostProcessor之后,exposedObject和bean相等,那么直接将二级缓存中的原始对象赋值给exposedObject,反之在经过其他BeanPostProcessor之后对bean做出了修改,即狸猫换太子这种说法,那么此时就会产生一个代理对象,和原始对象不相等,那么此时就要将最开始的exposedObject对象进行覆盖,将正真的对象放入二级缓存中
还剩最后一个问题:AService第4步判断已经进行过AOP呢?
追一下源码源码中是如何判断:
在AService中第四步正常进行AOP所以直接执行
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
//正常进行AOP
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 这里实现了对beanName的对象是否已经AOP作出判断
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
经过分析在三级缓存之上还有两个集合:
// 保存bean对象正在创建的名字的
private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 记录某个bean是否提前进行AOP了
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
Spring无法解决的循环依赖
@Component
@Scope("prototype")
public class AService{}
@Component
@Scope("prototype")
public class BAervice{}
这种情况是无法解决的,分析一下:
AService依赖BService,于是去创建BService,在创建BService发现依赖AService,但是由于AService是一个原型Bean,他不会去单例池中查找,而是直接再去创建一个AService,而此时又发现依赖BService,同样BService也是一个原型Bean,所以也直接创建一个BService,而它填充属性的时候,发现依赖AService,所以又去创建AService,形成一个闭环。
还有一种就是构造依赖:
这个比较好理解,在第一步实例化bean的时候还没有将bean对象放入三级缓存就去开始BService的生命周期了,BService也一样
@Component
public class AService{
private BService bService
public AService(BService bService){
this.bService = bService
}
}
@Component
public class BAervice{
private AService aService
public BService(AService aService){
this.aService = aService
}
}
在构造方法上加个Lazy注解试试??
本文地址:https://blog.csdn.net/buhuiguowang/article/details/112001837