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