使用@Async異步注解導致該Bean在循環依賴時啟動報BeanCurrentlyInCreationException異常的根本原因分析,以及提供解決方案【享學Spring】


每篇一句
面試造飛機,工作擰螺絲。工作中你只需要知道那些調用命令怎么使用就行,但背后的邏輯你有必要去了解

前言
今天在自己工程中使用@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());
}
}

 

本以為像解決事務問題一樣,像這樣寫是肯定完美解決問題的。但奈何帶來了新問題,啟動即報錯:

報錯信息如上~~~
1
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);
}
}
1
2
3
4
5
6
7
這樣配置后,容器啟動將不再報錯了,但是但是但是: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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
注意此@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();方式解決同類方法調用的方案,由於這種方式也是一個較大的話題,限於篇幅,且聽緊鄰的下文分解~
————————————————
版權聲明:本文為CSDN博主「_YourBatman」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/f641385712/article/details/92797058


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM