添加@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í)行的。所以

  • 之前的情況是:
  1. 分別生成PayNotifyService類的實(shí)例A和PayService類的實(shí)例B,
  2. 通過@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)飛哥