为了模拟循环依赖和多次动态代理,使用以下代码,可以看到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."); } } } } 复制代码