Java @Async注解導致spring啟動失敗解決方案詳解
前言
在這篇文章里,最后總結(jié)處,我說了會講講循環(huán)依賴中,其中一個類添加@Async有可能會導致注入失敗而拋異常的情況,今天就分析一下。
一、異常表現(xiàn),拋出內(nèi)容
1.1循環(huán)依賴的兩個class
1.CycleService1
@Service public class CycleService1 { @Autowired private CycleService2 cycleService2; @WangAnno @Async public void doThings() { System.out.println("it's a async move"); } }
2.CycleService2
@Service public class CycleService2 { private CycleService1 cycleService1; public void init() { } @WangAnno public void alsoDo() { System.out.println("create cycleService2"); } }
1.2 啟動報錯
Bean with name ‘cycleService1' has been injected into other beans [cycleService2] 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.
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:654)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:851)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:884)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:552)
at com.wang.Test.main(Test.java:109)
二、原因分析
2.1 主要原因
想想我在這篇博客里的圖解步驟:
- 當Spring在進行bean的實例化的時候,由于CycleService1和CycleService2是循環(huán)依賴的,
- 同時,由于CycleService1創(chuàng)建早于CycleService2。
- 所以,在CycleService1對CycleService2的initializeBean方法執(zhí)行之后得到了exposedObject,要從二級緩存里獲取CycleService1的earlySingletonReference不為null,就需要比較exposedObject和raw CycleService是否還是同一個對象,如果不再是同一個對象,那么就會報錯。
- 為什么有這個邏輯呢?
- 其實是因為如果能從二級緩存里拿出的earlySingletonReference不為null,說明了在該對象再創(chuàng)建過程中被其他對象循環(huán)依賴了,且調(diào)用了三級工廠中該對象的ObjectFactory方法,基于raw bean生成了對象放入到了二級緩存。但是當raw bean執(zhí)行完initializeBean之后生成了新的對象,那就出問題了。如下圖:
也就是說基于raw bean,得到了兩個基于該raw bean生成的proxy對象,Spring容器不知道最終該在容器里保存哪一個了。
2.2 循環(huán)依賴放入二級緩存處邏輯
1.每個bean在進行屬性注入之前,默認都會往Spring容器中放入一個ObjectFactory進入三級工廠,以便自己在屬性注入的時候被循環(huán)依賴時調(diào)用生成對象
if (earlySingletonExposure) { // 返回一個進行了aop處理的ObjectFactory,提前暴露 // 但是只有當該實例在創(chuàng)建過程中還被其他實例引用(循環(huán)依賴),才會被調(diào)用getEarlyBeanReference // 此處是第四次調(diào)用beanPostProcessor,不一定會調(diào)用,只有當該類真的在創(chuàng)建過程中被其他類當做屬性所依賴 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
2.所以在創(chuàng)建CycleService1過程中,CycleService2去注入CycleService2之前在三級工廠里放入了自己的ObjectFactory對象,然后在CycleService2創(chuàng)建過程中,要注入CycleService1的時候,就會調(diào)用Spring容器中的getEarlyBeanReference(beanName, mbd, bean)獲取CycleService1,下面我們來看看該方法調(diào)用的具體邏輯
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; }
然后咱們debug發(fā)現(xiàn),只有AbstractAutoProxyCreator#getEarlyBeanReference方法,有具體實現(xiàn)邏輯
public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }
具體的細節(jié),我們就不進入的,主要就是通過調(diào)用wrapIfNecessary生成了raw bean的aop proxy bean,后面放入了二級緩存。
2.3 initializeBean生成的對象
在initializeBean方法里會調(diào)用applyBeanPostProcessorsAfterInitialization方法
@Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
在循環(huán)里面會調(diào)用postProcessAfterInitialization方法
重點關注AbstractAutoProxyCreator的該方法實現(xiàn):
@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 對bean進行proxy操作 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
會發(fā)現(xiàn)AbstractAutoProxyCreator#postProcessAfterInitialization里面的具體邏輯就是判斷這個類有沒有調(diào)用過wrapIfNecessary,如果調(diào)用過就不再調(diào)用,就是保證同一個raw bean不會被多次proxy,同時提前暴露注入到其他對象里的就是proxy bean。
但是由于該bean(CycleService1)上加了@Async注解,此次也會觸發(fā)AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization,而這個方法,我們在這篇文章里講過了,正是@Async注解能生效的關鍵邏輯。所以此處生成了一個具有Async功能的新的async proxy bean
2.4 再次分析原因
基于2.3和2.4,我們基于raw bean得到了二級緩存里的aop proxy bean和async proxy bean。
讓我們再回憶一下判斷邏輯:
//此處是從二級緩存里面根據(jù)beanName拿出對象,因為二級緩存里放入的是因為循環(huán)依賴給其他bean注入的代理對象 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } // 我們之前早期暴露出去的Bean跟現(xiàn)在最后要放到容器中的Bean不是同一個 // allowRawInjectionDespiteWrapping為false // 并且當前Bean被當成依賴注入到了別的Bean中 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // 獲取到當前Bean依賴的Bean String[] dependentBeans = getDependentBeans(beanName); // 要得到真實的依賴的Bean Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { // 移除那些僅僅為了類型檢查而創(chuàng)建出來 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."); } } }
簡而言之,也就是此時從二級緩存里拿到了aop proxy bean,同時了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薬sync proxybean,Spring容器基于raw bean得到了兩個proxy bean,無法處理了。所以在使用@Async注解時,盡量不要在被循環(huán)依賴的Class上添加
解決方案
打破循環(huán)依賴
目前我能想到的方法就是打破循環(huán)依賴,因為循環(huán)依賴發(fā)生在bean生命周期的–屬性注入階段,所以我們需要做的就是打破這種循環(huán)依賴
1.延遲注入(使用@Lazy注解)
@Service public class CycleService1 { @Lazy @Autowired private CycleService2 cycleService2; @WangAnno @Async public void doThings() { cycleService2.alsoDo(); System.out.println("it's a async move"); } }
看過這篇文章的都知道原理了,此處不再累贅
2. 手動延遲注入(使用applicationContext.getBean)
@Service public class CycleService1 { @Autowired private ApplicationContext applicationContext; private CycleService2 cycleService2; @WangAnno @Async public void doThings() { if (Objects.isNull(cycleService2)) { cycleService2 = applicationContext.getBean(CycleService2.class); } cycleService2.alsoDo(); System.out.println("it's a async move"); } }
其實效果是上面加了@Lazy效果是一樣的,不過是我們自己在方法執(zhí)行的過程中手動進行延遲注入而已。
總結(jié)
從二級緩存里拿到earlySingletonReference(aop proxy bean),同時了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薳xposedObject(async proxy bean),Spring容器基于raw bean得到了兩個proxy bean,無法處理了。
所以在使用@Async注解時,盡量不要在被循環(huán)依賴的Class上添加
實在非要添加,可以看看我給出的解決方法。
到此這篇關于Java @Async注解導致spring啟動失敗解決方案詳解的文章就介紹到這了,更多相關Java @Async注解導致spring啟動失敗解決方案內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
分析那些不講武德的SDK(構(gòu)造使用規(guī)范)
這篇文章主要為大家介紹了盤點分析那些不講武德的SDK(構(gòu)造規(guī)范)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05Java中invokedynamic字節(jié)碼指令問題
這篇文章主要介紹了Java中invokedynamic字節(jié)碼指令問題,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-04-04springboot微服務項目集成html頁面的實現(xiàn)
本文主要介紹了springboot微服務項目集成html頁面的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04如何使用stream從List對象中獲取某列數(shù)據(jù)
這篇文章主要介紹了如何使用stream從List對象中獲取某列數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12