「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」
Spring代码
上面已经解释了:
- 为什么会出现循环依赖
- 以及如何解决循环依赖
上述总结
到现在算是分析完解决循环依赖问题了,流程如下:
A的:
-
放beanNameA到singletonsCurrentlyInCreation这个容器里。【0】
-
实例化A,得到一个对象,并把这个还没填充属性的A放到缓存A(singletonFactories)中。【1】
-
填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取有没有生成过提前暴露的,找不到就去缓存A中取,还获取不到就去创建B。【2】
接下来进入到B的生命周期:
- 放beanNameB到singletonsCurrentlyInCreation这个容器里。
- 实例化B,得到一个对象,并把这个还没填充属性的B放到缓存A中。
- 填充B中的A属性(根据A属性类型,名称去查找),首先去单例池中获取A(getSingleton()),获取不到单例A就去提前暴露bean(earlySingletonObjects)中取,获取不到就取缓存A(singletonFactories)中的并生成,此时能获取到了,就赋值,并放到earlySingletonObjects中。【4】
- 填充其他属性,初始化前BP,初始化,初始化后,放入单例池。
随后回到A的生命周期:
- 填充其他属性,初始化前BP,初始化,初始化后【6】,放入单例池。
所以回过头来看,为什么Spring的循环依赖需要这样解决?原因如下:
-
Spring需要支持单例,因此需要第一级缓存singletonObjects
-
Spring需要支持多线程下bean在填充完毕之前不能提前暴露到单例池中,因此需要第二级缓存earlySingletonObjects
-
Spring需要支持代理,因此需要第三级缓存singletonFactories
这里的三个缓存:
-
singletonObjects(单例池,这里放的是完全实例化且初始化好的bean)
-
earlySingletonObjects(这里放的是提前实例化的bean,这里并没有经过完整的生命周期,存在意义:提前把这个bean存起来,就是用来解决多个循环依赖的时候,保证bean的单例性质)
-
singletonFactories(打破循环的关键,只有在这里存了,才能保证二级缓存的内容可以正确生成(AOP相关))
-
如果这里使用二级缓存,那么就把earlySingletonObjects和singletonFactories混合在一起即可,即:在【1】中我们把实例化的对象就放到这里,这样做也是解决了循环依赖,但这样子就不能保证如果需要代理对象时,代理对象的单例性质(没有地方去保存)。
工厂方法生成 - 对应【1】
//其实在进入【1】之前,需要判断:
//1.是否单例?2.是否允许循环依赖?3.是否单例在创建当中
//如果不支持循环依赖就会往下走,就不会往第三级缓存取,出现了循环依赖就会报错
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//这里就对应上面的【1】
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//看,这里做的事情,就是:往第三级缓存里存,并从二级缓存里把对应的bean清除
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);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
而这里的getEarlyBeanReference实际上是BP的通知式循环调用:
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;
}
默认的实现实际上只有AbstractAutoProxyCreator:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
B尝试获取A实例填充 - 对应【4】
在填充属性的时候,就回去调用getBean方法(在前面的依赖注入部分可以trace):
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
//这里
Object sharedInstance = getSingleton(beanName);
//.......
}
@Nullable
//这里的逻辑就是【4】中Spring的实现
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先查看单例池中有没有
Object singletonObject = this.singletonObjects.get(beanName);
//如果现在不在创建中就跳出取创建了;如果并不是,那么就进入【4】中的逻辑了
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//这里就从二级缓存里取了,如果这里获取到了,那么依然会跳出
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//接下去就和【4】的描述一摸一样了:单例池->二级->三级
synchronized (this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
//这里注意,三级缓存生成了对象之后,就会把三级缓存对应的移除(因为已经完成了该做的所有事情)
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
- 这里有一个问题:为什么要加锁?
//接下去就和【4】的描述一摸一样了:单例池->二级->三级
synchronized (this.singletonObjects) {}
为什么要加锁?
因为这一步,需要保证原子性:
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
这里需要让这两级缓存中,同一个beanName只能从这两级缓存中获取一个对象。
如果前面已经执行AOP代理了,那么这里【6】如何处理?
上述的代码看起来已经解决一大半问题了,但是这里我们需要注意:
- 无论是AOP生成的代理对象,还是原始对象,在单例模式下我们都只需要一个bean。
在【1】和【4】中我们知道了是在AbstractAutoProxyCreator中进行了提前的AOP。
在步骤【6】中,我们会执行下面这个回调:
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;
}
这里就是通过earlyProxyReferences判断是否在【4】进行了AOP。
- 如果前面进行了AOP,那么这里就会把原始的bean返回。
那么这里就有个问题:如何获取到代理对象,放到单例池的缓存中?
- 我们可以从二级缓存中,来获取代理对象。
在bean的生命周期中最后的部分,会来进行这部分的判断:
//abstractAutowireCaplableBF
//这个变量在上面经过提前AOP之后,就是true了
if (earlySingletonExposure) {
//看这个变量名称,说明这里就是从二级缓存中取得
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//这里就是看看:前面暴露得对象,是否是原始bean?如果是的话,让暴露的对象变成AOP代理对象,接下去代理对象和原始对象不是同一个对象的问题就解决了,我们使用代理对象来完成后面的生命周期
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
//进入到这里,不一样的情况就出现了:
//1.出现原因:除了AOP代理了,还有其他的BP也对bean代理了【7】
//细看这里的代码,其实只是对进行处理并抛出的,相当于Spring认为走到这里,就已经是异常事件了【8】
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
【7】:exposedObject在这里最后被改变了:
exposedObject = initializeBean(beanName, exposedObject, mbd);这个方法会调用
applyBeanPostProcessorsBeforeInitialization这里的BP,也可以对bean做代理的包装,比如:@Async注解会往BP的list里加入
AsyncAnnotationBP这个BP在AOP的BP之后,因此【7】的地方就有可能在这里被再一次代理,导致bean和exposedObject不一致,再往下就可能会抛异常。
但是如果是@Transactional注解,则不会报错:
- 原因是:@Transactional注解工作方式和@Async不同,并不是通过添加BP来实现代理的,添加的是
TransactionManagementConfigurationSelector,因此在这里不会触发。这部分和AOP相关,后续再看看AOP。
【8】:抛异常的原因:因为这里如果发生了再次代理,那么放到单例池中的对象A,和注入给别的对象AA,这两个对象不一致,同样地违反了单例规则。
如果这里确实需要多次代理,一个解决方案是:
- 对于代理的对象,加一个@Lazy注解。
这样子在生命周期中,A注入的B对象就不会走B的生命周期,而是赋一个被Lazy相关BP生成的代理对象的值。
这样子调用B的时候,才会去实际获取B(获取或者创建)。
同时,如果是B上有代理,A上没有代理,也不会出问题,原因如下:
- 因为我们这里是:
- A先进入生命周期
- 在A注入B的过程中完成了B的生命周期,在这个步骤中发生了提前AOP。
- B生命周期完成了,A完成后续一系列步骤,最后放入单例池之前发生了错误。
那么,我们如果让B不仅是AOP也是Async,这里其实就没问题:
- 因为B的AOP和Async,都在初始化后的BP回调中执行的
那么初始化后回调的结果的代理对象,只会是同一个,这里就不会报错了:其实也就是说,这里的earlySingletonExposure就会是false,就不会报错了。
如果bean是原型的呢?
如果AB都是圆形的,Spring是否能解决循环依赖?
很遗憾的是,Spring中对于原型bean,并不能解决原型中循环依赖的问题。
因为:
- 每一次bean的创建,属性都需要是新的。
因此,上面说的解决方案,只针对单例,而非原型。
但是,如果循环依赖的依赖链上,有一个是单例的,就可以解决。
循环依赖的其他情况
构造方法的循环依赖
不能解决。因为这种情况下,对象都无法创建,也就是说A生命周期的实例都无法生成,上面的解决方案都没办法往下进行。
一个折衷的解决方案是:
- 在构造方法上,加上@Lazy注解。
自己注入自己
A中注入了A,按照定义,这也算是一种循环依赖。
流程是相同的,步骤也是相同的:因为有第三级缓存。