每篇一句
面試造飛機,工作擰螺絲。工作中你只需要知道那些調用命令怎麼使用就行,但背後的邏輯你有必要去了解
前言
今天在自己工程中使用@Async
的時候,碰到了一個問題:Spring循環依賴(circular reference)問題
。
或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了循環依賴問題嗎,它是支持循環依賴的呀?怎麼會呢?
不可否認,在這之前我也是這麼堅信的,而且每次使用得也屢試不爽。倘若你目前也和我有一樣堅挺的想法,那麼相信本文能讓你大有收貨~~。
我通過實驗總結出,出現使用@Async
導致循環依賴問題的必要
條件:
- 已開啓
@EnableAsync
的支持 @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
不生效的根本原因都是同一個:直接調用了本類方法而非接口方法/代理對象方法
。
解決這類不生效
問題的方案一般我們都有兩種:
自己注入自己
,然後再調用接口方法(當然此處的一個變種是使用編程方式形如:AInterface a = applicationContext.getBean(AInterface.class);
這樣子手動獲取也是可行的~~~本文不討論這種比較直接簡單的方式)- 使用
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容器內~
針對上面的步驟,爲了輔助理解,我嘗試總結文字描述如下:
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
生成的
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
官方也不推薦循環依賴,但是一個是理想情況,一個現實情況,它倆是有差距和差異的。
現實使用中,特別是業務開發
中循環依賴可以說是幾乎避免不了的,因此知其然而知其所以然後,才能徹底的大徹大悟,遇到問題不再蒙圈。