循环依赖

262 阅读14分钟

这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

背景

​ 今天在开发过程中,同事在一个xxxService中的某个方法上添加了@Async注解,本意是希望这个方法可以异步执行,但是添加注解之后,发现启动报错。

报错信息如下:

Bean with name 'xxxService' has been injected into other beans [xxxAService] in its raw version as part of a circular reference, but has eventually been wrapped...

从报错信息中可以明显的知道,是因为spring bean之间出现了循环依赖。以往只记得一个结论,spring是支持循环依赖,且spring中通过三级缓存来解决循环依赖出现的问题。正好借这个问题,且最近刚好在开spring启动流程,对bean的生命周期有大概的了解。

Bean的创建过程

​ 实际上一个Spring Bean的创建包含了三个过程:实例化、注入依赖项、初始化,在spring中分别对应的是instantiationpopulateinitialization。在没有看spring之前,我一度以为实例化和初始化是一个意思,一直以为两个单词是一个意思。

  • 实例化(instantiation):通过指定的静态函数或者实例化对象的某个函数,或者是无参构造函数等方式创建一个空对象。
  • 注入依赖项(populate):比如实现了BeanPostProcessorInstantiationAwareBeanPostProcessor开放的扩展点进行特定情况的依赖项注入,比如在属性上标记的@Autowird,则是通过AutowiredAnnotationBeanPostProcessor进行注入处理。
  • 初始化(initilization):注入依赖项之后的一些操作,比如指定了初始化initMethod方法,或者是BeanPostProcessor#postProcessBeforeInitialization接口等。

Springbean的创建过程,主要是从AbstractApplicationContext#refresh方法说起,这个方法涉及到了Spring bean的整个生命周期。

  1. 准备刷新容器操作(设置一些状态位,监听器、事件器)
  2. 实例化一个Bean工厂(DefaultListableBeanFactory)
  3. 初始化BeanFactory, 进行一些属性的初始化赋值
  4. 空方法扩展点(springboot中有使用到)
  5. 调用BeanFactoryPostProcessor
  6. 注册所有的BeanPostProcessor
  7. 国际化
  8. 初始化事件广播器
  9. 扩展点, 由子类实现
  10. 注册事件监听器
  11. 实例化所有的非懒加载的单例对象
@Override
public void refresh() {
    synchronized (this.startupShutdownMonitor) {
        // 1.
        prepareRefresh();

        // 2.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3.
        prepareBeanFactory(beanFactory);

        try {
            // 4.
            postProcessBeanFactory(beanFactory);

            // 5.
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6.
            registerBeanPostProcessors(beanFactory);

            // 7.
            initMessageSource();

            // 8.
            initApplicationEventMulticaster();

            // 9.
            onRefresh();

            // 10.
            registerListeners();

            // 11.
            finishBeanFactoryInitialization(beanFactory);

            finishRefresh();
        }
    }
}

创建Bean的三个过程,在AbstractAutowireCapableBeanFactory#doCreateBean方法中均有体现。bean对象是否需要提前暴露: 需满足三个条件 -> 1.是单例; 2.支持循环依赖; 3.bean对象正在创建中(在singletonsCurrentlyInCreation中能获取到)

整个过程可以最核心可以分为以下三步骤。 ①.实例化对象 ②.填充属性 ③.初始化对象:填充属性之后进行初始化及一些扩展点的操作

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException {
    if (instanceWrapper == null) {
        // ①
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }

    // bean是否需要提前暴露
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 添加三级缓存工厂
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    Object exposedObject = bean;
    try {
        // ②
        populateBean(beanName, mbd, instanceWrapper);
        // ③
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
}

下面逐个来简单分析下。

1.实例化对象

通过以下代码分析,实例化其实就是通过这几种方式,创建了一个空对象,并获取这个对象的引用。

  1. 获取这个beanclass属性,确保beanDefinitionbeanClass属性已经完成解析
  2. 通过beanDefinition中的supplier实例化这个bean,一般情况下不会通过这种方式创建
  3. 通过FactoryMethod实例化这个bean通过工厂方式实例化(包含静态工厂和实例工厂)@Bean是通过实例工厂(实例工厂也被spring容器管理[获取对应Bean之后通过反射调用实例化对象])来实例化对象xml方式中可以指定class为静态类factory-method为静态方法
  4. 下面这段代码都是在通过构造函数实例化这个Bean,分两种情况,一种是通过默认的无参构造,一种是通过推断出来的构造函数
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd,  Object[] args) {
    // 1.
    Class<?> beanClass = resolveBeanClass(mbd, beanName);

    // 2.
    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
    if (instanceSupplier != null) {
        return obtainFromSupplier(instanceSupplier, beanName);
    }

    // 3.
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }

    // 4.
    boolean resolved = false;
    boolean autowireNecessary = false;
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                resolved = true;
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }

    // 获取构造函数
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);

    return instantiateBean(beanName, mbd);
}

2.注入依赖项

主要的逻辑就是根据BeanPostProcessor扩展点的接口,或者实现它的二次扩展接口。通过这些扩展接口的实现,来进行特殊方式的注入,比如使用@Autowird

  1. 是否允许注入校验, 如果postProcessAfterInstantiation返回false则表示不允许注入直接跳出populateBean方法
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
    // ...省略部分代码
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            // 1.
            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                return;
            }
        }
    }
}

3.初始化对象

主要逻辑就是在注入依赖项之后,进行一些对象完善,可以通过制定初始化方法initMethod或者通过实现BeanPostProcessor后置处理器来处理一些操作。

  1. 调用aware扩展点方法
  2. 执行BeanPostProcessor#postProcessBeforeInitialization
  3. 执行指定的初始化initMethod方法
  4. 执行BeanPostProcessor#postProcessAfterInitialization
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        // ...
    }else {
        // 1. 
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 2. 
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // 3. 
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 4. 
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

小结:从大方向来说,spring创建bean的过程就是以上三个过程。而循环依赖的主要处理逻辑应该是在注入依赖项这一环,判定是否循环依赖是在初始化方法之后进行判定。

三级缓存

spring中通过三级缓存来解决循环依赖,三级缓存分别为:

  • singletonObjects:一级缓存,保存的是初始化完成的单例Bean对象
  • earlySingletonObjects:二级缓存,保存的是实例化之后,注入依赖项之前的对象,换句话说就是一个空对象的引用。
  • singletonFactories:三级缓存,保存的是需要提前暴露的对象的单例工厂,二级缓存中的对象引用,是由这个工厂生成。

AbstractAutowireCapableBeanFactory#doCreateBean方法的,实例化对象之后,注入依赖项之前,bean是否需要提前暴露: 需满足三个条件 -> 1.是单例; 2.支持循环依赖; 3.bean正在创建中

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // 添加三级缓存工厂
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

进入添加三级缓存工厂对应的方法DefaultSingletonBeanRegistry#addSingletonFactory,这里会将这个实例化bean的工厂添加到三级缓存中。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

实例化bean的工厂方法,这个方法大致的作用就是对传入的这个bean对象, 进行满足一下条件的BeanPostProcessor的处理, 默认情况下只有AOP的一个BeanPostProcessor。在目标bean无需AOP的情况下, 返回的其实就是传入的bean,也就是仅仅只经过实例化的bean

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;
}

这里举一个简单的例子,来演示spring创建bean过程中三级缓存的变化。假设需要往spring容器中添加两个对象A、B,且这两个对象相互依赖,且spring先处理A在处理B

简单通过文字描述下整个流程(当然了三级缓存中的一级缓存中的创建完成的bean不止这两个对象,还包含spring内置的一些对象):

A对象的创建过程

  1. A对象实例化:这时仅通过A对象的无参构造函数,创建了一个空对象。

  2. 将A提前暴露的工厂添加到第三级缓存中:通过addSingletonFactory()方法,这个方法的第二个参数,是个函数式接口,核心逻辑就是使得在第一步实例化的这个空对象提前被满足条件的BeanPostProcessor处理后暴露出来。(所以经过这一步骤后三级缓存中的第三级缓存工厂中添加了一个提交暴露A对象的工厂方法。)

  3. A对象依赖注入:假设A对象需要引入的B对象是通过@Autowird属性注入的方式注入的,那么这时会进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties进行属性遍历解析后注入,而在这个时间节点,B对象还未创建,所以进入B对象的创建流程。

    B对象的创建过程

    3.1 B对象实例化:这时仅通过B对象的无参构造函数创建了一个空的B对象。

    3.2 添加B对象的提前暴露工厂:同理步骤2。(所以经过这一步骤后三级缓存中的第三级缓存工厂中又添加了一个提交暴露B对象的工厂方法。在这个时间节点,这个工厂列表中就有两个元素)。

    3.3 B对象依赖注入:同理步骤3的前半步骤。由于这个时候去三级缓存中的一级和二级缓存中获取不到A对象,则进入第三级缓存,通过第三级缓存,获取到了A对象的提前暴露对象,同时移除第三级缓存中A的工厂对象。(这时第二级缓存中存在一个A的提前暴露对象,第三级缓存中仅剩B对象的提前暴露工厂)

    3.4 B对象的初始化:执行一些指定初始化方法,aware方法等。(这时第一级缓存中就有一个创建完成的B对象,第二级缓存中存在一个A的提前暴露对象,第三级缓存中无元素) ==>准确的说这个第一级缓存的添加B对象和第三级缓存中的移除提前暴露B对象的工厂操作并不是在初始化(initializeBean)方法中,而是在获取到B对象之后,有一个是否创建了实例的校验。具体代码在DefaultSingletonBeanRegistry#getSingleton

    方法的finally之后,有兴趣可以自行查看。

    这时回到了A对象的注入流程,而A对象就可以获取到创建好的B对象,将B通过反射注入到自身属性中。(所以经过这一步骤后第二级缓存中存在一个A的提前暴露对象,第三级缓存中仅剩B对象的提前暴露工厂)

  4. 最后A对象的实例化:与3.4步骤同理。最后三级缓存中仅有一级缓存中存在两个元素。

小结:其实分析下来,整个过程并不难理解,提前暴露到二级缓存中的空对象,其实是每个bean创建的第一步,也就是每个bean都首先会经过一次创建空对象的过程,然后将这个空对象是否需要暴露的决定权,添加到第三级缓存的工厂对象中,如果出现其他对象引用的时候,则提交暴露这个空对象,让依赖对象主体持有这个空对象的引用,这样并不会影响到被依赖对象后续的注入与初始化。

上述的创建流程是最理想的情况,而比较复杂的情况是3.3步骤中,通过第三级缓存中获取到A对象,有可能并不是在步骤1中创建的空对象 ,而是经过一些BeanPostProcessor处理过的代理对象,比如AOP代理。

在看两个个例子:

  1. A与B分别通过@Autowired注入对方,且B被LogAspect进行AOP处理。这种情况下,在3.3步骤B去获取A的提前暴露对象后,B在初始化方法中经过AOP变成一个代理对象返回给A对象。
@Service
public class A {

    @Autowired
    private B b;

}

@Service
public class B {

    @Autowired
    private A a;

    public void printHelloWorld(){

    }
}

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(public * cn.com.xiaocainiaoya.cyclic.B.printHelloWorld(..))")
    private void testPointcut() {}

    @Around("testPointcut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        log.info("LogAspect before");
        point.proceed();
        log.info("LogAspect after");
    }
}
  1. A与B分别通过@Autowired注入对方,且A被LogAspect进行AOP处理。这种情况下,在3.3步骤B去获取A的提前暴露对象时,获取的是一个代理对象,B将这个代理对象的引用挂在自身声明的A属性处后,将B对象返回。
@Service
public class A {

    @Autowired
    private B b;
    public void printHelloWorld(){

    }
}

@Service
public class B {

    @Autowired
    private A a;
}

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(public * cn.com.xiaocainiaoya.cyclic.A.printHelloWorld(..))")
    private void testPointcut() {}

    @Around("testPointcut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        log.info("LogAspect before");
        point.proceed();
        log.info("LogAspect after");
    }
}

判定规则

​ 其实判定是否是循环依赖报错并不是在上面说的创建过程三个步骤中,而是在每一个对象经过这三个步骤后,要添加到spring容器中之前有一个判断逻辑,来进行判定。进入到AbstractAutowireCapableBeanFactorydoCreateBean方法中。

  1. 从三级缓存中获取提前暴露对象
  2. 若存在, 有可能是一级缓存也有可能是二级缓存
  3. 比较经过实例化创建的bean和经过初始化之后的exposeObject是否相等
  4. 若相等 --> 将提前暴露的对象注入到容器中
  5. 判断这个beanName对应的bean的依赖是否都创建完成若依赖都创建完成, 则将初始化的exposedObject注入到容器中
if (earlySingletonExposure) {
    // 1
    Object earlySingletonReference = getSingleton(beanName, false);
    // 2
    if (earlySingletonReference != null) {
        // 3
        if (exposedObject == bean) {
            // 4
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            // 5
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
        }
    }
}

示例

​ 以上中把springbean的创建过程大致整理了一下,但是似乎还没有回答上文背景中提出的问题,为什么在某个方法上加了一个@Async注解之后,导致出现了循环依赖报错,而上文中有两个简单小例子,其中使用了AOP,却没有出现循环依赖报错?(如果对spring中的AOP@Async实现方式有一个大概了解的话,应该知道二者都是通过生成代理对象的方式实现。)

这里举两个个例子:

  1. A对象和B对象相互依赖,且A对象中的printHelloWorld标记@Async
@Service
public class A {

    @Autowired
    private B b;

    @Async
    public void printHelloWorld(){

    }
}

@Service
public class B {

    @Autowired
    private A a;
}


  1. A对象和B对象相互依赖,且A对象中的printHelloWorldAOP
@Service
public class A {

    @Autowired
    private B b;

    public void printHelloWorld(){

    }
}

@Service
public class B {

    @Autowired
    private A a;
}

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(public * cn.com.xiaocainiaoya.cyclic.A.printHelloWorld(..))")
    private void testPointcut() {}

    @Around("testPointcut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        log.info("LogAspect before");
        point.proceed();
        log.info("LogAspect after");
    }
}

​ 在第一个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获得的A对象是A在创建过程中的空对象。当A的经过初始化之后(在initialization()方法中会执行实现了BeanPostProcessor后置处理器中的postProcessAfterInitialization方法,而@Async的实现就是AsyncAnnotationBeanPostProcessor,经过该方法后生成的代理对象为proxyA),所以就导致A对象提前暴露给B对象注入的对象与最后初始化生成的对象不一致,根据上文说的判定逻辑,最终导致循环依赖报错。

循环依赖2.png

​ 在第二个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获取的A对象是经过BeanPostProcessor后置处理器的一个子扩展接口SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法,处理之后变为代理对象proxyA,(这里通过提前暴露对象的工厂获取到的就是代理对象,也就是提前暴露的对象就是代理对象),所以进入上文的判定逻辑,正常返回。

循环依赖2-1.png

注意:二者之前的差别就是提前暴露的对象是不是代理对象。获取说是代理对象的暴露时机有所不同。那么为什么二者暴露代理对象的时机有所不同?二者都是通过BeanPostProcessor的实现进行处理,AOP实现类和@Async的实现类AnnotationAwareAspectJAutoProxyCreatorAsyncAnnotationBeanPostProcessor有一个很大的区别是前者实现了SmartInstantiationAwareBeanPostProcessor接口,在提前暴露对象工厂中,只会执行实现了这个接口的getEarlyBeanReference方法来获取提前暴露对象。

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;
}