最近項目中使用@Async注解在方法上引起了循環依賴報錯:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Bean with name 'classA' has been injected into other beans [classB] 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.
先提幾個問題:
1.@Async是什么
2.什么是循環依賴
3.為什么使用@async會引起循環依賴
4.如何解決
一、@Async
從Spring3開始提供了@Async注解,該注解可以被標注在方法上(也可以標注在類上,代表所有方法都異步調用),以便異步地調用該方法。調用者將在調用時立即返回,方法的實際執行將提交給Spring TaskExecutor的任務中,由指定的線程池中的線程執行。
看定義很簡單,其實就是新起一條線程執行注解下的方法,所以可以立即返回結果無需等待,常用於調用外部接口。
二、循環依賴
簡單講就是有A,B兩個服務,A依賴B,B依賴A,這樣就是一個死循環。如下圖:
用代碼表示:
@component public class A{ @Autowired private B b; } @component public class B{ @Autowired private A a; }
三、為什么使用@async會引起循環依賴
其實當我們使用Spring的時候,默認是會解決循環依賴,但當使用了@Async注解方法后,處理循環依賴就失效了。
@Component public class A{ @Autowired private B b; @Async @Override public void testA() { } }
@Component public class ClassB{ @Autowired private A a; @Override public void testB() { a.testA(); } }
如果要知道底層的前因后果,需要去分析源碼,這里我用別人總結的話簡單說一下:
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最終就拋錯了
四、如何解決
1.使用@Lazy或者 @ComponentScan(lazyInit = true)
@Component public class ClassB{ @Autowired @Lazy private A a; @Override public void testB() { a.testA(); } }
2.使用setter注入
@Component public class ClassA{ private B b; public void setB(B b) { this.b = b; }
@Async @Override public void testA() { b.testb(); } }
@Component public class ClassB{ private A a; public void setA(A a) { this.a = a; } @Override public void testB() { a.testA(); } }
3.使用@Autowired注解
@Component public class A{ private B b; @Autowired public void SetB(B b) { this.b= b; } @Async @Override public void testA() { //TODO } }
4.重構方法
重新建class,把@Async的方法放在新的類中,從根本上消除循環依賴