添加@EnableAsync注解后報循環(huán)依賴,注入失敗咋辦
情景再現(xiàn)
在PayService類中注入了payNotifyService的實例,而在PayNotifyService類中又注入了payService的實例。而PayNotifyService類中又有一個加了@Async
注解的方法A。
今天在公司項目中想將一個耗時的流程放在異步線程中執(zhí)行,然后,按照操作手冊,熟練了在異步方法上添加了@Async
注解,在對應模塊的啟動類中添加了@EnableAsync
,妥妥的,準備跑一波看看效果。結果傻眼了。
報錯啦?。。。。?!
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘payNotifyService’: Bean with name ‘payNotifyService’ has been injected into other beans [PayService] 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 ‘a(chǎn)llowEagerInit’ flag turned off, for example.
粗一看 ,這循環(huán)注入了嘛!奇怪了。沒加@EnableAsync
之前項目跑了好好的,加了這個注解之后就報錯了,咋回事呢,小老弟。事不宜遲,google走一波。
原因分析
眾所周知,在SpringBoot項目中添加@EnableAsync
是使@Async
注解生效的。之前沒加這個注解的時候異步方法都是沒有生效的。而我們Async 是通過AOP生成代理類來實現(xiàn)異步執(zhí)行的。所以
- 之前的情況是:
- 分別生成PayNotifyService類的實例A和PayService類的實例B,
- 通過
@Autowired
注解將實例注入到對應的類中。所以啟動沒問題。
- 現(xiàn)在的情況是:
前面兩步的情況一樣,但是當我們Spring的IOC容器檢查到@Async
注解之后,會通過AOP這個方法所在的類生成一個代理類。注入的時候發(fā)現(xiàn)該Bean已經(jīng)被其他對象注入了,所以這就出現(xiàn)了問題了。
解決辦法
針對上面分析的原因,我們對應的有兩種解決辦法,
- 第一種方法
將@Autowired
注解和@Lazy
搭配使用,使之注入的是代理類的Bean,而不是原始的Bean。
例如這樣:
@Autowired
@Lazy
private payNotifyService payNotifyService;
- 第二種方法
將擁有@Async
注解的方法集中到一個類中,統(tǒng)一管理,也使得代碼清晰易懂,便于維護,個人比較推薦這種方式。 - 第三種方法
使用基于 Setter 的注入,不通過@Autowired
注解注入,而通過setter方法注入,這種方法,治標不治本,個人不建議。
擴展:異步調用獲取返回結果:
如果我們想獲取異步調用的返回結果?該如何處理呢?
答案就是在將方法的返回參數(shù)類型定義成Future<Object>
,例如:
@Async
public Future<String> doTaskOne() throws Exception {
System.out.println("開始做任務一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
return new AsyncResult<>("任務一完成");
}
如上,是一個通過@Async
修飾的異步方法。我們將真正需要的結果封裝在Futrue
中,實際返回時我們只需要通過new AsyncResult<>(”任務一完成“)
。
外部方法調用該異步方法,獲取返回結果只需要如下:
doTaskOne().get()
作者:碼農(nóng)飛哥
微信公眾號:碼農(nóng)飛哥