前言
最近項目啓動的時候瘋狂拋錯,具體以下: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