Spring是如何通过三级缓存来解决循环依赖的

一、三级缓存分别是什么?

说到循环依赖,我们首先要知道2点。
1、只有单例Bean之间才会存在循环依赖。原因是:原型Bean每次获取到的都是新对象,如果原型Bean之间存在循环依赖,那么不就进入死循环了?当然Spring在处理这种情况时是直接抛出BeanCurrentlyInCreationException异常。
2、Spring只解决了属性注入情况下的循环依赖,构造函数的循环依赖无法解决。原因是:属性注入的情况下可以通过先实例化Bean对象,然后再互相注入。而构造函数在创建对象时就需要提供依赖的属性,这个时候两者互相依赖,不提供属性就无法创建对象,不创建对象就无法提供属性,就好像发生了死锁一样,故无法解决。

本文主要就根据源码学习一下Spring是如何通过三级缓存来解决循环依赖的?
直接看源码AbstractBeanFactory类,Spring获取一个Bean的调用路径通常为:getBean-doGetBean-getSingleton。如果能获取到单例Bean就直接返回,获取不到则进行创建Bean的流程。通过getSingleton方法的代码

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    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;
}

可以看到getSingleton方法中存在3个Map对象,这3个Map对象就是传说中的三级缓存,分别是:
1、singletonObjects
2、earlySingletonObjects
3、singletonFactories

我们通过一步步的拆解来了解这3个缓存的作用

二、不考虑循环依赖的情况下,一个单例Bean是如何创建及复用的?(一级缓存)

Spring创建Bean的逻辑主要在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean中。
对于单例Bean来说,会先调用上面的getSingleton方法从singletonObjects中获取Bean,获取到的Bean为null的话,则调用getSingleton的重载方法

sharedInstance = getSingleton(beanName, () -> {
    try {
        return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
        // Explicitly remove instance from singleton cache: It might have been put there
        // eagerly by the creation process, to allow for circular reference resolution.
        // Also remove any beans that received a temporary reference to the bean.
        destroySingleton(beanName);
        throw ex;
    }
});

重载方法的第二个参数传入了一个ObjectFactory工厂,而创建Bean的过程就在这个工厂的实现中,也就是createBean(beanName, mbd, args);方法。

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    if (logger.isTraceEnabled()) {
        logger.trace("Creating instance of bean '" + beanName + "'");
    }
    RootBeanDefinition mbdToUse = mbd;

    // Make sure bean class is actually resolved at this point, and
    // clone the bean definition in case of a dynamically resolved Class
    // which cannot be stored in the shared merged bean definition.
    Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
    if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
        mbdToUse = new RootBeanDefinition(mbd);
        mbdToUse.setBeanClass(resolvedClass);
    }

    // Prepare method overrides.
    try {
        mbdToUse.prepareMethodOverrides();
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                beanName, "Validation of method overrides failed", ex);
    }

    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                "BeanPostProcessor before instantiation of bean failed", ex);
    }

    try {
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
        // A previously detected exception with proper bean creation context already,
        // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
                mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
    }
}

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    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");
        }
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            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.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

代码逻辑比较多,但是精简之后其实就是图中4个部分:

1、创建Bean对象,其实就相当于new Bean();只是Spring用的比较高级,是通过反射进行实例化Bean的。
2、创建好Bean对象之后,Bean对象中的属性都还是空的,这个时候就需要使用populateBean方法来对Bean中的属性进行填充,也就是属性注入。
属性注入的逻辑是通过继承了BeanPostProcessor接口的InstantiationAwareBeanPostProcessor接口中的AutowiredAnnotationBeanPostProcessor类来实现,方法为postProcessProperties。我们暂且知道在这个方法里面会通过Spring容器去获取属性需要的Bean对象并进行注入即可。
3、我们知道Spring为Bean的生命周期提供了很多扩展点,而这些扩展点的调用就是在initializeBean这个方法里面。在这个方法里面会按照顺序依次调用Aware接口、BeanPostProcessorsBefore方法、Init方法、BeanPostProcessorsAfter方法。具体的逻辑也可以暂且搁置,我们只需要知道在这个阶段,有这么一些逻辑会对Bean再次加工处理即可。
4、完成上述工作之后,现在的Bean就已经完全初始化好了。作为一个单例Bean,最重要的点就是复用,所以要把这个单例Bean放入一级缓存singletonObjects中,对应到最开头的代码就可以实现后续使用时直接获取而不需要每次都重新创建。将单例Bean放入一级缓存的代码需要看getSingleton的重载方法中的实现

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName,
                        "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                                "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
            }
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                // Has the singleton object implicitly appeared in the meantime ->
                // if yes, proceed with it since the exception indicates that state.
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

ObjectFactory工厂中的singletonFactory.getObject();就对应调用了createBean(beanName, mbd, args);方法,所以在该方法返回之后的singletonObject就是新创建的Bean对象,之后通过addSingleton(beanName, singletonObject);方法将单例Bean添加进一级缓存。

注:综上可以认为,Spring所有的单例Bean最终都会存在于一级缓存singletonObjects中,故而singletonObjects是一个并发安全的ConcurrentHashMap。如果不考虑循环依赖时,一级缓存其实就够用了,那么为什么需要二三级缓存呢?

三、当发生循环依赖时,如果解决?(二级缓存)

举个循环依赖的样例,现有A、B两个Bean,A中的属性为B,B中的属性为A,也就是说A、B之间互相循环依赖。如果只有一级缓存的话,那么创建A、B的流程就会变成下面的样子

红框部分出现了死循环,A和B都无法完成属性注入的操作,也就无法进入到下一步,那么如何解决这个问题呢?
思考一下,在A属性注入B的时候,A需要关心B是否完全完成了初始化吗?答案是不需要,只需要一个引用即可,而这个引用在实例化对象之后就可以拿到。所以Spring引入了二级缓存earlySingletonObjects,在实例化Bean之后,就提前将Bean放入二级缓存中,方便在发生循环依赖被其他Bean引用,当然这个时候Bean还未完成后续操作还是个半成品。引入二级缓存的流程图如下,按照红色序号的顺序看流程。

代码的部分,主要是有两块
1、将实例化后的Bean提前放入二级缓存
代码是在doCreateBean方法中的addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));这里其实跟三级缓存有关,但是我们可以先忽略getEarlyBeanReference方法中的逻辑,简化为直接返回当前创建好的Bean
2、从二级缓存中读取提前暴露的Bean
代码是在最初的getSingleton方法中,如果allowEarlyReference参数的值为true且需要的Bean处于创建中,这个时候就可以从二级缓存中获取。获取的部分同样涉及到了三级缓存,如果二级缓存中没有Bean的话,会从三级缓存中获取一个工厂来获取Bean,然后放入二级缓存。这里的工厂对应1中的getEarlyBeanReference方法,按照1所说的,我们暂且忽略getEarlyBeanReference方法中的逻辑,也就是返回值就是刚实例化完成的Bean。

注:综上,通过将Bean对象提前暴露放到二级缓存的形式,可以解决A、B之间的循环依赖问题。回过头来看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;
}

这个方法里面做了些什么呢?其实他跟动态代理有关,也就是三级缓存存在的原因。

四、如果Bean需要被动态代理,会有什么问题?(三级缓存)

在看这个问题前,我们需要先了解一下,一个Bean是在哪个环节进行的动态代理呢?

回头看最早的一张图,在初始化Bean的过程中,initializeBean方法中可以有很多扩展点,而动态代理就是在这些扩展点中处理的,具体的逻辑是通过众多BeanPostProcessor中的AbstractAutoProxyCreator的postProcessAfterInitialization来实现的。

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

    return bean;
}

先忽略判断条件以及wrapIfNecessary方法中具体进行动态代理的部分,可以看到Bean在完成postProcessAfterInitialization之后,如果需要被动态代理的话,Bean就会变成一个代理的Bean即ProxyBean。

由于代理Bean的逻辑基本是在创建Bean的结尾部分完成的,所以假如只有二级缓存的存在,发生循环依赖时,注入的Bean就不会是最终单例池中的ProxyBean而是原始的Bean,这就会有问题。怎么解决呢?解决思路跟二级缓存的思路一致,那就是提前代理。

回到二级缓存中我们暂时忽略的getEarlyBeanReference方法,在这个方法中,会对Bean做一次SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法的调用。刚好用来做动态代理的AbstractAutoProxyCreator类实现了这个接口。并且在getEarlyBeanReference方法中提前把动态代理的逻辑做完了。

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

也就是说,如果一个Bean他需要被代理,并且发生了循环依赖,则这个Bean会被提前动态代理,而实现这块的操作是通过工厂的模式来实现的,并且代理只需要做一次,所需Spring引入了第三级的缓存,并通过工厂的形式将代理的操作包装起来,存于第三级缓存singletonFactories中。

到这里可能会有一个疑问,我不需要三级缓存,在实例化完Bean之后就粗暴的先完成代理,然后放入二级缓存可不可以?
从实现的角度来说,我觉得是完全ok的,只是在正常的理解思路中,代理就应该在一个Bean完成所有初始化工作之后才进行,只是由于循环依赖的原因被迫要把代理前置。如果粗暴的在实例化完Bean后就进行代理,显得有点本末倒置。所以就需要一个机制来让提前代理延迟触发,且在有需要的时候才触发(发生循环依赖的时候),这就是需要三级缓存最重要的原因。

可能还会有一个疑问,如果我将二级缓存去除,只用三级缓存可不可行?
答案是不行,因为如果只用三级缓存,那么发生多次循环依赖时(A依赖B、C,B依赖A,C也依赖A),那么A就会被动态代理多次。但是如果我们能保证三级缓存的工厂在返回对象时,保证只动态代理一次,之前被代理过的对象直接返回是不是就可以了?其实是可以的,只是设想一下,三级缓存的工厂要实现这个逻辑,是不是他内部也要有一个缓存才行,那不就是相当于二级缓存挪到了工厂内部而已吗,没什么区别。

在postProcessAfterInitialization方法中,我们忽略了判断条件,现在回头看这个判断条件,他存在的意义就是,如果这个Bean已经被提前代理过了,那么在正常的代理流程中,他就不能再次代理,直接返回即可,但是注意,这里入参的bean和直接返回的bean还是原始的Bean,那么在加入单例池之前,是在哪里将这个原始Bean替换成代理后的Bean的呢?
在doCreateBean方法的结尾处有这么一段代码

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        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.");
            }
        }
    }
}

在将创建好的Bean放入单例池之前,会调用getSingleton方法再次获取一次Bean对象,但是入参的allowEarlyReference为false,也就是说从二级缓存中获取一次Bean对象,如果Bean被提前代理过,并且经过正常流程的initializeBean之后的Bean没有被其他扩展方法代理过,也就是证明提前代理是ok的,这个时候就会把原始的Bean替换成为提前代理过的ProxyBean。

五、总结

1、为了实现单例的复用,一级缓存是必须要存在的,并且是使用最高频的一个缓存
2、为了解决循环依赖的问题,引入了二级缓存,通过提前暴露对象引用的办法来实现,但是引出了动态代理的问题
3、为了解决2中的动态代理问题,引入了三级缓存,将提前暴露对象引用及提前动态代理的操作封装为一个工厂,并且这个工厂只有在发生循环依赖的时候,也就是必要的时候才会进行触发,实现了延迟触发的操作,尽量避免操作前置。

六、其他

流程图


觉得内容还不错?打赏个钢镚鼓励鼓励!!👍