Spring getBean是如何解决循环依赖和多次动态代理

By | 2019-12-08

通过此文你可以了解Spring:

  • refreshContext里的getBean做了什么事情
  • BeanPostProcessor中多个拓展点的调用时机
  • 动态代理的入口
  • 解决循环依赖的机制
  • 同时面对循环依赖和多次动态代理的做法

切入问题的代码

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

1.refreshContext里的getBean做了什么事情

Spring作为IOC容器,一眼看上去十分简单,无非就是初始化对象然后放到map里,需要的时候直接从map取。Spring的总体思路确实如此,但细节上面对Factory类、Advisor类、循环依赖、动态代理等各种特殊情况做出处理,引出三级缓存用来处理这些特殊情况。

  • singletonObjects:一级缓存,保存完成初始化的单例bean实例
  • earlySingletonObjects:保存早期暴露的单例bean实例
  • singletonFactories:保存单例bean的工厂函数对象

getBean里面涉及到各级缓存,直接上图

整个getBean可以分为4大部分,分别是getBean、doGetBean、createBean和doCreateBean,如果单例实例已经创建好,就会直接在doGetBean里将实例返回,如果尚未创建,则会调用createBean和doCreateBean的一系列创建过程,可以简单概括为三部分:

  • createBeanInstance:利用构造方法等途径new出实例
  • populateBean:往实例中注入各个属性
  • initializeBean:对实例进行一些后续初始化工作

一些细节的逻辑在上图中已经标示,这里不再赘述。

2.BeanPostProcessor中多个拓展点的调用时机

Spring中提供了若干个BeanPostProcessor接口(下称BPP),BPP提供了在不同的时间点让用户对bean进行自定义调整的机会,大多数都在上图用黄色泡泡特别标示了,有以下几种BPP接口比较常用:

  • postProcessMergedBeanDefinition:可对BeanDefinition添加额外的自定义配置
  • getEarlyBeanReference:返回早期暴露的bean引用,一个典型的例子是循环依赖时如果有动态代理,需要在此先返回代理实例
  • postProcessAfterInstantiation:在populateBean前用户可以手动注入一些属性
  • postProcessProperties:对属性进行注入,例如配置文件加密信息在此解密后注入
  • postProcessBeforeInitialization:属性注入后的一些额外操作
  • postProcessAfterInitialization:实例完成创建的最后一步,这里也是一些BPP进行代理的时机

3.动态代理的入口

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;
}
复制代码

4.解决循环依赖的机制

结合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;
}
复制代码

5.同时面对循环依赖和多次动态代理的做法

网上很多文章提到这三级缓存是为了解决循环依赖,但细想一下,面对循环依赖的情况,只需把依赖的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.");
            }
        }
    }
}
复制代码

总结

  • Spring提供了多样的BeanPostProcessor供用户介入实例化过程,Spring自身也使用了这些BPP进行动态代理等工作,如果你在开发一些框架功能,熟悉使用BPP能让你事半功倍
  • 一个普通孤立bean的实例化十分简单,经过createBeanInstance,populateBean,initializeBean等步骤即完成
  • 在populateBean阶段会注入其他依赖的bean,如果该bean尚未创建,会首先执行其实例化
  • 如果存在循环依赖,则动用到三级缓存将其提前暴露出来,如果没有循环依赖,则不会提前暴露
  • 如果同时存在循环依赖和多次动态代理,可使用@Lazy等方法延迟实例化解决

作者:sabersword
链接:https://juejin.im/post/5dec9fe76fb9a01608236cd3

发表评论

邮箱地址不会被公开。 必填项已用*标注