使用@Async非同步註解導致該Bean在迴圈依賴時啟動報BeanCurrentlyInCreationException異常的根本原因分析,以及提供解決方案
前言
今天在自己工程中使用@Async
的時候,碰到了一個問題:Spring迴圈依賴(circular reference)問題
。 或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了迴圈依賴問題嗎,它是支援迴圈依賴的呀?怎麼會呢?
不可否認,在這之前我也是這麼堅信的,而且每次使用得也屢試不爽。倘若你目前也和我有一樣堅挺的想法,那麼相信本文能讓你大有收貨~~。
不得不提,關於@Async
的使用姿勢,請參閱: 【小家Spring】Spring非同步處理@Async的使用以及原理、原始碼分析(@EnableAsync) 關於Spring Bean的迴圈依賴問題,請參閱: 【小家Spring】一文告訴你Spring是如何利用"三級快取"巧妙解決Bean的迴圈依賴問題的
我通過實驗總結出,出現使用@Async
導致迴圈依賴問題的必要
條件:
- 已開啟
@EnableAsync
的支援 @Async
註解所在的Bean被迴圈依賴了
背景
若你是一個有經驗的程式設計師,那你在開發中必然碰到過這種現象:事務不生效。
關於事務不生效方面的原因,可參考:【小家java】Spring事務不生效的原因大解讀
本文場景的背景也一樣,我想呼叫本類的非同步方法(標註有@Async
註解),很顯然我知道為了讓於@Async
生效,我把自己依賴進來,然後通過service介面來呼叫,程式碼如下:
@Service public class HelloServiceImpl implements HelloService { @Autowired private HelloService helloService; @Override public Object hello(Integer id) { System.out.println("執行緒名稱:" + Thread.currentThread().getName()); helloService.fun1(); // 使用介面方式呼叫,而不是this return "service hello"; } @Async @Override public void fun1() { System.out.println("執行緒名稱:" + Thread.currentThread().getName()); } }
此種做法首先是Spring中一個典型的迴圈依賴場景:自己依賴自己
。本以為能夠像解決事務不生效問題一樣依舊屢試不爽
,但沒想到非常的不給面子,啟動即報錯:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'helloServiceImpl': Bean with name 'helloServiceImpl' has been injected into other beans [helloServiceImpl] 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:622) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ...
這裡說明一下,為什麼有小夥伴跟我說:我使用@Async
即使本類方法呼叫也從來木有遇到這個錯誤啊?難道它不常見? 為此經過我的一番調查,包括看一些同事、小夥伴的程式碼發現:並不是使用@Async
沒有啟動報錯,而是他本類呼叫的時候直接呼叫的方法,這樣@Async
是不生效的但小夥伴卻全然不知而已。
至於@Async
沒生效這種問題為何沒報出來???甚至過了很久很久都沒人發現和關注?? 其實道理很簡單,它和事務不生效不一樣,@Async
若沒生效99%情況下都不會影響到業務的正常進行,因為它不會影響資料正確性,只會影響到效能(無非就是非同步變同步唄,這是相容的)。 但是呢,我期望的是作為一個技術人,還是能夠有一定的技術敏感性
。能夠迅速幫助自己或者你身邊同事定位到這個問題,這或許是你可以出彩的資本吧~
我們知道事務不生效
和@Async
不生效的根本原因都是同一個:直接呼叫了本類方法而非介面方法/代理物件方法
。 解決這類不生效
問題的方案一般我們都有兩種:
自己注入自己
,然後再呼叫介面方法(當然此處的一個變種是使用程式設計方式形如:AInterface a = applicationContext.getBean(AInterface.class);
這樣子手動獲取也是可行的~~~本文不討論這種比較直接簡單的方式)- 使用
AopContext.currentProxy();
方式
本文就講解採取方式一自己注入自己
的方案解決帶來了更多問題
,使用AopContext.currentProxy();
方式會在緊鄰的下篇博文裡詳解~
注意:
自己注入自己
是能夠完美解決事務不生效問題。如題,本文旨在講解解決@Async
的問題~~~有的小夥伴肯定會說:讓不呼叫本類的
@Async
方法不就可以了;讓不產生迴圈依賴不就可以了;這都是解決方案啊~ 其實你說的沒毛病,但我我想說:理想的設計當然是不建議迴圈依賴的。但在真實的業務開發
中迴圈依賴是100%避免不了的,同樣本類方法的互調也同樣是避免不了的~
關於@Async
的使用和原理,有興趣的可以先補補課: 【小家Spring】Spring非同步處理@Async的使用以及原理、原始碼分析(@EnableAsync)
自己依賴自己
方案帶來的問題分析
說明:所有示例,都預設
@EnableAsync
已經開啟~ 所以示例程式碼中不再特別標註
自己依賴自己
這種方式是一種典型的使用迴圈依賴
方式來解決問題,大多數情況下它是一個非常好的解決方案。 比如本例若要解決@Async
本類呼叫問題,我們的程式碼會這麼來寫:
@Service public class HelloServiceImpl implements HelloService { @Autowired private HelloService helloService; @Transactional @Override public Object hello(Integer id) { System.out.println("執行緒名稱:" + Thread.currentThread().getName()); // fun1(); // 這樣書寫@Async肯定不生效~ helloService.fun1(); //呼叫介面方法 return "service hello"; } @Async @Override public void fun1() { System.out.println("執行緒名稱:" + Thread.currentThread().getName()); } }
本以為像解決事務問題一樣,像這樣寫是肯定完美解決問題的。但奈何帶來了新問題
,啟動即報錯:
報錯資訊如上~~~
BeanCurrentlyInCreationException
這個異常型別小夥伴們應該並不陌生,在迴圈依賴
那篇文章中(請參閱相關閱讀)有講述到:文章裡有提醒小夥伴們關注報錯的日誌,有朝一日肯定會碰面
,沒想到來得這麼快~
對如上異常資訊,我大致翻譯如下:
建立名為“helloServiceImpl”的bean時出錯:名為“helloServiceImpl”的bean已作為迴圈引用的一部分注入到其原始版本中的其他bean[helloServiceImpl]中, **但最終已被包裝**。這意味著其他bean不使用bean的最終版本。
問題定位
本著先定位問題才能解決問題的原則
,找到問題的根本原因成為了我現在最需要做的事。從報錯資訊的描述可以看出,根本原因是helloServiceImpl
最終被包裝(代理),所以被使用的bean並不是最終的版本,所以Spring的自檢機制報錯了~~~
說明:Spring管理的Bean都是單例的,所以Spring預設需要保證所有
使用
此Bean的地方都指向的是同一個地址,也就是最終版本的Bean,否則可能就亂套了,Spring也提供了這樣的自檢機制~
上面文字敘述有點蒼白,相信小夥伴們看著也是一臉懵逼、二臉繼續懵逼吧。下面通過示例程式碼分析看看結果。
為了更好的說明問題,此處不用自己依賴自己
來表述(因為名字相同容易混淆不方便說明問題),而以下面A、B兩個類的形式說明:
@Service public class A implements AInterface { @Autowired private BInterface b; @Async @Override public void funA() { } } @Service public class B implements BInterface { @Autowired private AInterface a; @Override public void funB() { a.funA(); } }
如上示例程式碼啟動時會報錯:(示例程式碼模仿成功)
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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:622) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ...
下面是重點,來跟蹤一下原始碼,定位此問題:
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
上:
// @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容器內~
針對上面的步驟,為了輔助理解,我嘗試總結文字描述如下:
context.getBean(A)
開始建立A,A例項化完成後給A的依賴屬性b開始賦值~context.getBean(B)
開始建立B,B例項化完成後給B的依賴屬性a開始賦值~- 重點:此時因為A支援迴圈依賴,所以會執行A的
getEarlyBeanReference
方法得到它的早期引用。而執行getEarlyBeanReference()
的時候因為@Async
根本還沒執行,所以最終返回的仍舊是原始物件
的地址 - B完成初始化、完成屬性的賦值,此時屬性field持有的是Bean A
原始型別
的引用~ - 完成了A的屬性的賦值(此時已持有B的例項的引用),繼續執行初始化方法
initializeBean(...)
,在此處會解析@Aysnc
註解,從而生成一個代理物件
,所以最終exposedObject
是一個代理物件(而非原始物件)最終加入到容器裡~ 尷尬場面
出現了:B引用的屬性A是個原始物件,而此處準備return的例項A竟然是個代理物件,也就是說B引用的並非是最終物件
(不是最終放進容器裡的物件)- 執行自檢程式:由於
allowRawInjectionDespiteWrapping
預設值是false,表示不允許上面不一致的情況發生,so最終就拋錯了~
此步驟是由我個人即興總結,希望能幫助到小夥伴們理解。若有不對的地方,還請指出讓幫忙我斧正
解決方案
通過上面分析,知道了問題的根本原因,現總結出解決上述新問題
的解決方案,可分為下面三種方案:
- 把
allowRawInjectionDespiteWrapping
設定為true - 使用
@Lazy
或者@ComponentScan(lazyInit = true)
解決 - 不要讓
@Async
的Bean參與迴圈依賴
1、把allowRawInjectionDespiteWrapping
設定為true:
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true); } }
這樣配置後,容器啟動將不再報錯了,但是但是但是:Bean A的@Aysnc
方法將不起作用了,因為Bean B裡面依賴的a是個原始物件,所以它最終沒法執行非同步操作(即使容器內的a是個代理物件):
需要注意的是:但此時候Spring容器裡面的Bean A是Proxy代理物件的~~~
但是此種情況若是正常依賴(非迴圈依賴)的a,注入的是代理物件,@Async
非同步依舊是會生效的哦~
這種解決方式一方面沒有達到真正的目的(畢竟Bean A上的@Aysnc
沒有生效)。
由於它只對迴圈依賴內的Bean受影響,所以影響範圍並不是全域性,因此當找不到更好辦法的時候,此種這樣也不失是一個不錯的方案,所以我個人對此方案的態度是不建議,也不反對。
2、使用@Lazy
或者@ComponentScan(lazyInit = true)
解決
本處以使用@Lazy
為例:(強烈不建議使用@ComponentScan(lazyInit = true)
作用範圍太廣了,容易產生誤傷)
@Service public class B implements BInterface { @Lazy @Autowired private AInterface a; @Override public void funB() { System.out.println("執行緒名稱:" + Thread.currentThread().getName()); a.funA(); } }
注意此
@Lazy
註解加的位置,因為a最終會是@Async
的代理物件,所以在@Autowired
它的地方加 另外,若不存在迴圈依賴而是直接引用a,是不用加@Lazy
的
只需要在Bean b的依賴屬性上加上@Lazy
即可。(因為是B希望依賴進來的是最終的代理物件進來,所以B加上即可,A上並不需要加)
最終的結果讓人滿意:啟動正常,並且@Async
非同步效果也生效了,因此本方案我是推薦的
但是需要稍微注意的是:此種情況下B裡持有A的引用和Spring容器裡的A並不是同一個
,如下圖:
兩處例項a的地址值是不一樣的,容器內的是$Proxy@6914
,B持有的是$Proxy@5899
。
關於
@Autowired
和@Lazy
的聯合使用為何是此現象,其實@Lazy
的代理物件是由ContextAnnotationAutowireCandidateResolver
生成的,具體參考博文:【小家Spring】Spring依賴注入(DI)核心介面AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier註解的原理
3、不要讓@Async
的Bean參與迴圈依賴 顯然如果方案3如果能夠解決它肯定是最優的方案。奈何它卻是現實情況
中最為難達到的方案。 因為在實際業務開發
中像迴圈依賴、類內方法呼叫等情況並不能避免,除非重新設計、按規範改變程式碼結構,因此此種方案就見仁見智吧~
為何@Transactional即使迴圈依賴也沒有問題呢?
最後回答小夥伴給我提問的這個問題:同為建立動態代理物件,同為一個註解標註在類上 / 方法上,為何@Transactional
就不會出現這種啟動報錯呢?
其實這個問題的答案在上篇文章的後半拉已經解釋了,詳見 【小家Spring】一文告訴你Spring是如何利用"三級快取"巧妙解決Bean的迴圈依賴問題的
雖說他倆的原理都是產生代理物件,且註解的使用方式幾乎無異。so區別Spring對它哥倆的解析不同,也就是他們代理的建立的方式不同:
@Transactional
使用的是自動代理建立器AbstractAutoProxyCreator
,上篇文章詳細描述了,它實現了getEarlyBeanReference()
方法從而很好的對迴圈依賴提供了支援@Async
的代理建立使用的是AsyncAnnotationBeanPostProcessor
單獨的後置處理器實現的,它只在一處postProcessAfterInitialization()
實現了對代理物件的建立,因此若出現它被迴圈依賴
了,就會報錯如上~~~
so,雖然從表象上看這兩個註解的實現方式一樣,但細咬其實現過程細節上,兩者差異性還是非常明顯的。瞭解了實現方式上的差異後,自然就不難理解為何有報錯和有不報錯了~
最後,在理解原理的基礎上還需要注意如下這個case(加深理解),若是下面這種情況,其實是啟動不報錯且可以正常work的:
和上面示例相比:唯一區別是把
@Async
寫在bean B上而A沒有寫(上面是寫在bean A上而B中沒有寫)
@Service public class A implements AInterface{ @Autowired private BInterface b; @Override public void funA() { } } @Service public class B implements BInterface { @Autowired private AInterface a; @Async // 寫在B的方法上 這樣B最終會被建立代理物件 @Override public void funB() { a.funA(); } }
備註:若按照正常Spring容器會先初始化A,啟動就肯定是不會報錯的,這也就是我上面說的結論:這種情況下預設是可以work的
通過猜測也能夠猜到,A和B不是對等的關係,處理結果和Bean的初始化順序有關。
至於Spring對Bean的例項化、初始化順序,若沒有特別干預的情況下,它和類名字母排序有關~
為了說明問題,此處我人工干預先讓Spring容器初始化B(此處方案為使用@DependsOn("b")
):
@DependsOn("b") @Service public class A implements AInterface { ... }
這樣干預能夠保證B肯定在A之前初始化,然後啟動也就會報同樣錯誤:(當然此處報錯資訊是bean b):
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b': Bean with name 'b' has been injected into other beans [a] 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:622) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ...
若技術敏感點的小夥伴發現,此處能夠給我們一個解決
自己依賴自己
問題的另外一個思路,是否可以考慮干預一下Bean的初始化順序來達到正常啟動的目的呢? 理論上是可行的,但是在實操過程中個人不太建議這麼去幹(如果有更好的方案的話)~
總結
雖然Spring
官方也不推薦迴圈依賴,但是一個是理想情況,一個現實情況,它倆是有差距和差異的。 現實使用中,特別是業務開發
中迴圈依賴可以說是幾乎避免不了的,因此知其然而知其所以然後,才能徹底的大徹大悟,遇到問題不再蒙圈。
使用AopContext.currentProxy();
方式解決同類方法呼叫的方案,由於這種方式也是一個較大的話題,限於篇幅,且聽緊鄰的下文分解~
相關文章
- 24--Spring解決bean之間的迴圈依賴SpringBean
- Spring原始碼分析之迴圈依賴及解決方案Spring原始碼
- spring解決迴圈依賴Spring
- spring迴圈依賴解決過程&Bean的生命週期SpringBean
- SpringCloud BeanCurrentlyInCreationException 異常解決方案SpringGCCloudBeanException
- 解決rpm包迴圈依賴
- Spring 中 bean 的迴圈依賴SpringBean
- Spring框架是怎麼解決Bean之間的迴圈依賴的 (轉)Spring框架Bean
- Spring中非同步註解@Async的使用、原理及使用時可能導致的問題Spring非同步
- 深談Spring如何解決Bean的迴圈依賴SpringBean
- Spring 迴圈依賴的三種方式(三級快取解決Set迴圈依賴問題)Spring快取
- Spring如何使用三級快取解決迴圈依賴Spring快取
- springboot bean的迴圈依賴實現 原始碼分析Spring BootBean原始碼
- 【Spring】Spring中的迴圈依賴及解決Spring
- 【spring原始碼系列】之【Bean的迴圈依賴】Spring原始碼Bean
- Spring學習:簡單實現一個依賴注入和迴圈依賴的解決Spring依賴注入
- 在使用反射時,maven設定依賴範圍引起的異常反射Maven
- 解決一次gitlab因異常關機導致啟動失敗Gitlab
- 一文詳解spring迴圈依賴Spring
- 3.3 Spring5原始碼---迴圈依賴過程中spring讀取不完整bean的最終解決方案Spring原始碼Bean
- 再探迴圈依賴 → Spring 是如何判定原型迴圈依賴和構造方法迴圈依賴的?Spring原型構造方法
- Spring如何解決迴圈依賴?Spring
- maven依賴衝突以及解決方法Maven
- go 中的迴圈依賴Go
- 依賴衝突時的解決方法
- async / await:更好的非同步解決方案AI非同步
- 探索 JavaScript 中的依賴管理及迴圈依賴JavaScript
- Oracle RAC啟動因CTSS導致的異常Oracle
- SpringIOC迴圈依賴Spring
- Spring迴圈依賴Spring
- 淺談迴圈依賴
- spring是如何解決迴圈依賴的?Spring
- Spring是如何解決迴圈依賴的Spring
- 使用ReflectionTestUtils解決依賴注入依賴注入
- HelloWorld版的SpringMVC使用註解驅動的依賴注入SpringMVC依賴注入
- 在typescript專案中解決cycle依賴的一種方案TypeScript
- Spring原始碼分析:Spring的迴圈依賴分析Spring原始碼
- No bean named 'cacheManager' availablej 異常解決BeanAI