为了模拟循环依赖和多次动态代理,使用以下代码,可以看到DevController和TestController是互相依赖的,并且通过@Transactional和@Async来模拟多次动态代理
@Transactional
@RestController
public class DevController {
@Autowired
private TestController testController;
@GetMapping("dev")
@Async
public String dev() {
return "dev";
}
}
复制代码
@Transactional
@RestController
public class TestController {
@Autowired
private DevController devController;
@GetMapping("test")
@Async
public String test() {
return "test";
}
}
复制代码
Spring作为IOC容器,一眼看上去十分简单,无非就是初始化对象然后放到map里,需要的时候直接从map取。Spring的总体思路确实如此,但细节上面对Factory类、Advisor类、循环依赖、动态代理等各种特殊情况做出处理,引出三级缓存用来处理这些特殊情况。
getBean里面涉及到各级缓存,直接上图
整个getBean可以分为4大部分,分别是getBean、doGetBean、createBean和doCreateBean,如果单例实例已经创建好,就会直接在doGetBean里将实例返回,如果尚未创建,则会调用createBean和doCreateBean的一系列创建过程,可以简单概括为三部分:
一些细节的逻辑在上图中已经标示,这里不再赘述。
Spring中提供了若干个BeanPostProcessor接口(下称BPP),BPP提供了在不同的时间点让用户对bean进行自定义调整的机会,大多数都在上图用黄色泡泡特别标示了,有以下几种BPP接口比较常用:
Spring的动态代理是通过BPP实现的,其中AbstractAutoProxyCreator是十分典型的自动代理类,它实现了getEarlyBeanReference和postProcessAfterInitialization两个接口都是代理的逻辑,通过earlyProxyReferences缓存避免对同一实例代理两次。
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 将实例放进缓存里,说明已经代理过了
this.earlyProxyReferences.put(cacheKey, bean);
// wrapIfNecessary是根据Advisor的情况对bean实例进行代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
@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;
}
复制代码
结合getBean的工作流程,我们来看一下问题中的DevController和TestController是如何解决循环依赖的(假设DevController先于TestController构造)
其中关键步骤就是如下代码,描述了TestController如何从三级缓存中寻找DevController并进行实例化
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先从一级缓存中查找
Object singletonObject = this.singletonObjects.get(beanName);
// 一级缓存查不到而且bean实例正在创建,则从二级缓存中查找
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 二级缓存查找不到而且允许早期暴露引用,则从三级缓存中查找
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 三级缓存查到的话
if (singletonFactory != null) {
// 调用它的工厂方法构造出实例,并从三级缓存放到二级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
复制代码
网上很多文章提到这三级缓存是为了解决循环依赖,但细想一下,面对循环依赖的情况,只需把依赖的bean放进二级缓存就足够了,为何还需要三级缓存呢,我觉得三级缓存是用来解决循环依赖和动态代理同时存在的问题。
可以看到 4.解决循环依赖的机制 解释了三级缓存singletonFactories面对循环依赖的用法,那么二级缓存earlySingletonObjects则是加上了多次动态代理的时候使用
具体代码在doCreateBean的尾段,个人认为理解了这段代码,就基本上把Spring IOC最晦涩的部分理解清楚了
if (earlySingletonExposure) {
// 从一级缓存和二级缓存中查找bean实例,由于最后一个参数是false,因此不会查找三级缓存
Object earlySingletonReference = getSingleton(beanName, false);
// 在上述的循环依赖下,DevController被@Transactional动态代理,通过AbstractAutoProxyCreator被暴露到二级缓存中
// 此时会进入该分支
if (earlySingletonReference != null) {
// 如果在initializeBean中DevController没有被代理,符合此条件,最终返回被代理后的DevController
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 如果在initializeBean中DevController被代理了(例如@Async的AbstractBeanFactoryAwareAdvisingPostProcessor)
// 那么exposedObject != bean,会尝试下面的判断
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 如果DevController有依赖其他bean,本例中是依赖TestController,此时有两个DevController
// 分别是@Async和@Transactional代理过的,返回哪一个都不对,因此抛出异常
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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
复制代码