使用@Async異步註解導致該Bean在循環依賴時啓動報BeanCurrentlyInCreationException異常的根本原因分析

每篇一句

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

前言

今天在自己工程中使用@Async的時候,碰到了一個問題:Spring循環依賴(circular reference)問題
或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了循環依賴問題嗎,它是支持循環依賴的呀?怎麼會呢?

不可否認,在這之前我也是這麼堅信的,而且每次使用得也屢試不爽。倘若你目前也和我有一樣堅挺的想法,那麼相信本文能讓你大有收貨~~

我通過實驗總結出,出現使用@Async導致循環依賴問題的必要條件:

  1. 已開啓@EnableAsync的支持
  2. @Async註解所在的Bean被循環依賴了

背景

若你是一個有經驗的程序員,那你在開發中必然碰到過這種現象:事務不生效

本文場景的背景也一樣,我想調用本類的異步方法(標註有@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不生效的根本原因都是同一個:直接調用了本類方法而非接口方法/代理對象方法
      解決這類不生效問題的方案一般我們都有兩種:

      1. 自己注入自己,然後再調用接口方法(當然此處的一個變種是使用編程方式形如:AInterface a = applicationContext.getBean(AInterface.class);這樣子手動獲取也是可行的~~~本文不討論這種比較直接簡單的方式)
      2. 使用AopContext.currentProxy();方式

      本文就講解採取方式一自己注入自己的方案解決帶來了更多問題,使用AopContext.currentProxy();方式會在緊鄰的下篇博文裏詳解~

      注意:自己注入自己是能夠完美解決事務不生效問題。如題,本文旨在講解解決@Async的問題~~~

      有的小夥伴肯定會說:讓不調用本類的@Async方法不就可以了;讓不產生循環依賴不就可以了;這都是解決方案啊~
      其實你說的沒毛病,但我我想說:理想的設計當然是不建議循環依賴的。但在真實的業務開發中循環依賴是100%避免不了的,同樣本類方法的互調也同樣是避免不了的~

      自己依賴自己方案帶來的問題分析

      說明:所有示例,都默認@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容器內~


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

                  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最終就拋錯了~

                  此步驟是由我個人即興總結,希望能幫助到小夥伴們理解。若有不對的地方,還請指出讓幫忙我斧正

                  解決方案

                  通過上面分析,知道了問題的根本原因,現總結出解決上述新問題的解決方案,可分爲下面三種方案:

                  • 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生成的

                      3、不要讓@Async的Bean參與循環依賴
                      顯然如果方案3如果能夠解決它肯定是最優的方案。奈何它卻是現實情況中最爲難達到的方案。
                      因爲在實際業務開發中像循環依賴、類內方法調用等情況並不能避免,除非重新設計、按規範改變代碼結構,因此此種方案就見仁見智吧~


                      爲何@Transactional即使循環依賴也沒有問題呢?

                      最後回答小夥伴給我提問的這個問題:同爲創建動態代理對象,同爲一個註解標註在類上 / 方法上,爲何@Transactional就不會出現這種啓動報錯呢?

                      雖說他倆的原理都是產生代理對象,且註解的使用方式幾乎無異。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官方也不推薦循環依賴,但是一個是理想情況,一個現實情況,它倆是有差距和差異的。
                            現實使用中,特別是業務開發中循環依賴可以說是幾乎避免不了的,因此知其然而知其所以然後,才能徹底的大徹大悟,遇到問題不再蒙圈。

                            發表評論
                            所有評論
                            還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
                            相關文章