@Async註解使用不當引起的spring循環依賴思考

前言

最近項目啓動的時候瘋狂拋錯,具體以下:spring

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Bean with name 'classA' has been injected into other beans [classB] 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.緩存

大概意思是:建立名爲「classA」的bean時出錯:名爲「classA」的bean已做爲循環引用的一部分被注入其原始版本中的其餘bean[classB],但最終已被包裝。後來發現,是在一個方法上面添加了@Async註解致使的,去掉此註解後,項目得以正常啓動。app

循環依賴分析

咱們知道,spring注入bean的方式有三種:ide

1. 構造器注入
2. setter
3. 自動注入

可是咱們使用spring的時候,默認Spring是會給咱們解決循環依賴的,爲何呢?他使用的是哪一種方式?爲何加了一個註解@Async的時候,循環依賴就失效了呢?這些問題咱們一點點的來分析。
爲了方便分析,我寫了一個最簡單的小demo來講明此事,這個demo只有兩個類,ClassA和ClassB,其中A,B互相注入對方。若是不使用@Async咱們會發現項目能夠啓動,spring的成功的幫助咱們解決了循環依賴的問題。可是加入了@Async之後項目啓動不出來,拋出前面咱們所說的異常.下面是兩個依賴的關係:post

/**
* create by liuliang
 * on 2019-12-06  11:21
 */
@Component
public class ClassA implements InterfaceA{
     @Autowired
    private InterfaceB b;

    @Async
    @Override
    public void testA() {

    }
}

/**
 * create by liuliang
 * on 2019-12-06  11:22
 */
@Component
public class ClassB implements InterfaceB{
    @Autowired
    private InterfaceA a;

    @Override
    public void testB() {
        a.testA();
    }
}

根據拋錯,咱們定位一下異常錯誤的地方:this

protected Object doCreateBean( ... ){
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...

// populateBean這一句特別的關鍵,它須要給A的屬性賦值,因此此處會去實例化B~~
// 而B咱們從上能夠看到它就是個普通的Bean(並不須要建立代理對象),實例化完成以後,繼續給他的屬性A賦值,而此時它會去拿到A的早期引用
// 也就在此處在給B的屬性a賦值的時候,會執行到上面放進去的Bean A流程中的getEarlyBeanReference()方法  從而拿到A的早期引用~~
// 執行A的getEarlyBeanReference()方法的時候,會執行自動代理建立器,可是因爲A沒有標註事務,因此最終不會建立代理,so B合格屬性引用會是A的**原始對象**
// 須要注意的是:@Async的代理對象不是在getEarlyBeanReference()中建立的,是在postProcessAfterInitialization建立的代理
// 從這咱們也能夠看出@Async的代理它默認並不支持你去循環引用,由於它並無把代理對象的早期引用提供出來~~~(注意這點和自動代理建立器的區別~)

// 結論:此處給A的依賴屬性字段B賦值爲了B的實例(由於B不須要建立代理,因此就是原始對象)
// 而此處實例B裏面依賴的A注入的仍舊爲Bean A的普通實例對象(注意  是原始對象非代理對象)  注:此時exposedObject也依舊爲原始對象
populateBean(beanName, mbd, instanceWrapper);

// 標註有@Async的Bean的代理對象在此處會被生成~~~ 參照類:AsyncAnnotationBeanPostProcessor
// 因此此句執行完成後  exposedObject就會是個代理對象而非原始對象了
exposedObject = initializeBean(beanName, exposedObject, mbd);

...
// 這裏是報錯的重點~~~
if (earlySingletonExposure) {
    // 上面說了A被B循環依賴進去了,因此此時A是被放進了二級緩存的,因此此處earlySingletonReference 是A的原始對象的引用
    // (這也就解釋了爲什麼我說:若是A沒有被循環依賴,是不會報錯不會有問題的   由於若沒有循環依賴earlySingletonReference =null後面就直接return了)
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        // 上面分析了exposedObject 是被@Aysnc代理過的對象, 而bean是原始對象 因此此處不相等  走else邏輯
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        // allowRawInjectionDespiteWrapping 標註是否容許此Bean的原始類型被注入到其它Bean裏面,即便本身最終會被包裝(代理)
        // 默認是false表示不容許,若是改成true表示容許,就不會報錯啦。這是咱們後面講的決方案的其中一個方案~~~
        // 另外dependentBeanMap記錄着每一個Bean它所依賴的Bean的Map~~~~
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            // 咱們的Bean A依賴於B,so此處值爲["b"]
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

            // 對全部的依賴進行一一檢查~    好比此處B就會有問題
            // 「b」它通過removeSingletonIfCreatedForTypeCheckOnly最終返返回false  由於alreadyCreated裏面已經有它了表示B已經徹底建立完成了~~~
            // 而b都完成了,因此屬性a也賦值完成兒聊 可是B裏面引用的a和主流程我這個A居然不相等,那確定就有問題(說明不是最終的)~~~
            // so最終會被加入到actualDependentBeans裏面去,表示A真正的依賴~~~
            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.");
            }
        }
    }
}
...
}

這裏知識點避開不@Aysnc註解標註的Bean的建立代理的時機。
@EnableAsync開啓時它會向容器內注入AsyncAnnotationBeanPostProcessor,它是一個BeanPostProcessor,實現了postProcessAfterInitialization方法。此處咱們看代碼,建立代理的動做在抽象父類AbstractAdvisingBeanPostProcessor上:lua

// @since 3.2   注意:@EnableAsync在Spring3.1後出現
// 繼承自ProxyProcessorSupport,因此具備動態代理相關屬性~ 方便建立代理對象
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

// 這裏會緩存全部被處理的Bean~~~  eligible:合適的
private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);

//postProcessBeforeInitialization方法什麼不作~
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
    return bean;
}

// 關鍵是這裏。當Bean初始化完成後這裏會執行,這裏會決策看看要不要對此Bean建立代理對象再返回~~~
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisor == null || bean instanceof AopInfrastructureBean) {
        // Ignore AOP infrastructure such as scoped proxies.
        return bean;
    }

    // 若是此Bean已經被代理了(好比已經被事務那邊給代理了~~)
    if (bean instanceof Advised) {
        Advised advised = (Advised) bean;
    
        // 此處拿的是AopUtils.getTargetClass(bean)目標對象,作最終的判斷
        // isEligible()是否合適的判斷方法  是本文最重要的一個方法,下文解釋~
        // 此處還有個小細節:isFrozen爲false也就是還沒被凍結的時候,就只向裏面添加一個切面接口   並不要本身再建立代理對象了  省事
        if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
            // Add our local Advisor to the existing proxy's Advisor chain...
            // beforeExistingAdvisors決定這該advisor最早執行仍是最後執行
            // 此處的advisor爲:AsyncAnnotationAdvisor  它切入Class和Method標註有@Aysnc註解的地方~~~
            if (this.beforeExistingAdvisors) {
                advised.addAdvisor(0, this.advisor);
            } else {
                advised.addAdvisor(this.advisor);
            }
            return bean;
        }
    }

    // 若不是代理對象,此處就要下手了~~~~isEligible() 這個方法特別重要
    if (isEligible(bean, beanName)) {
        // copy屬性  proxyFactory.copyFrom(this); 生成一個新的ProxyFactory 
        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
        // 若是沒有強制採用CGLIB 去探測它的接口~
        if (!proxyFactory.isProxyTargetClass()) {
            evaluateProxyInterfaces(bean.getClass(), proxyFactory);
        }
        // 添加進此切面~~ 最終爲它建立一個getProxy 代理對象
        proxyFactory.addAdvisor(this.advisor);
        //customize交給子類複寫(實際子類目前都沒有複寫~)
        customizeProxyFactory(proxyFactory);
        return proxyFactory.getProxy(getProxyClassLoader());
    }

    // No proxy needed.
    return bean;
}

// 咱們發現BeanName最終實際上是沒有用到的~~~
// 可是子類AbstractBeanFactoryAwareAdvisingPostProcessor是用到了的  沒有作什麼 能夠忽略~~~
protected boolean isEligible(Object bean, String beanName) {
    return isEligible(bean.getClass());
}
protected boolean isEligible(Class<?> targetClass) {
    // 首次進來eligible的值確定爲null~~~
    Boolean eligible = this.eligibleBeans.get(targetClass);
    if (eligible != null) {
        return eligible;
    }
    // 若是根本就沒有配置advisor  也就不用看了~
    if (this.advisor == null) {
        return false;
    }
    
    // 最關鍵的就是canApply這個方法,若是AsyncAnnotationAdvisor  能切進它  那這裏就是true
    // 本例中方法標註有@Aysnc註解,因此鐵定是能被切入的  返回true繼續上面方法體的內容
    eligible = AopUtils.canApply(this.advisor, targetClass);
    this.eligibleBeans.put(targetClass, eligible);
    return eligible;
}
...
}

經此一役,根本原理是隻要能被切面AsyncAnnotationAdvisor切入(即只須要類/方法有標註@Async註解便可)的Bean最終都會生成一個代理對象(若已是代理對象裏,只須要加入該切面便可了~)賦值給上面的exposedObject做爲返回最終add進Spring容器內~.net

針對上面的步驟,爲了輔助理解,我嘗試總結文字描述以下:代理

1.context.getBean(A)開始建立A,A實例化完成後給A的依賴屬性b開始賦值~
2.context.getBean(B)開始建立B,B實例化完成後給B的依賴屬性a開始賦值~
3.重點:此時由於A支持循環依賴,因此會執行A的getEarlyBeanReference方法獲得它的早期引用。而執行getEarlyBeanReference()的時候由於@Async根本還沒執行,因此最終返回的仍舊是原始對象的地址
4.B完成初始化、完成屬性的賦值,此時屬性field持有的是Bean A原始類型的引用~
5.完成了A的屬性的賦值(此時已持有B的實例的引用),繼續執行初始化方法initializeBean(...),在此處會解析@Aysnc註解,從而生成一個代理對象,因此最終exposedObject是一個代理對象(而非原始對象)最終加入到容器裏~
6.尷尬場面出現了:B引用的屬性A是個原始對象,而此處準備return的實例A居然是個代理對象,也就是說B引用的並不是是最終對象(不是最終放進容器裏的對象)
7.執行自檢程序:因爲allowRawInjectionDespiteWrapping默認值是false,表示不容許上面不一致的狀況發生,so最終就拋錯了~code

以上是本身的理解加上網上參考資料所得,若有問題,還望各位極力斧正!
參考資料:
http://www.javashuo.com/article/p-stnwfmxu-nt.html

相關文章
相關標籤/搜索