springboot的異步調用@Async和事務@Transactional註解的注意事項

場景描述:我司最近要做一個上傳Word轉圖片,實現預覽Word的功能,整體流程爲用戶前端上傳word文件,上傳成功之後即提示成功,Java後臺異步調用轉圖片服務。

僞代碼:

controller層:接收上傳文件

serviceImpl層:1.上傳文件->2.上傳成功->3.異步調用轉圖片服務

以前只知道springboot中異步調用使用@Async註解,並且被註解標註的方法必須是public。於是就寫出了下面的代碼

// serviceImpl中部分方法
 @override
 public String Upload(File file){

  // 1.上傳
   Boolean result = upload(file);
   if(result){
      return "上傳失敗";
   }
  // 2.異步調用轉換圖片服務
  this.transformToPictures()
  // 3.直接返回上傳結果
  return "上傳成功"; 
}

@Async
public Re transformToPictures(){
  // 轉換圖片的邏輯
  return Re.ok();
}

寫完之後開始進行單元測試,問題就暴露了出來。當調用上傳圖片出現異常的時候,會直接影響主方法,從而提示前端,上傳出錯。其實已經上傳成功了。

根據出現的問題可知,其實上面所謂的異步方法並沒用異步執行,而是同步執行的。爲何加了@Async沒有生效的?查了一些資料,

1.Async返回的結果只有void和Fucure<V>l類型,所以上面的代碼返回Re顯然是不行的。

2.Async註解是基於動態代理實現的,具體什麼個原理我沒細查,如果你的異步方法是在當前類中書寫,並且在當前類使用則必須獲取當前類的代理,於是獲取當前類,代碼改成下面這樣。如果是當前類調用其他類的異步方法,則直接調用即可,無需獲取代理類。

// serviceImpl中部分方法
 @override
 public String Upload(File file){

  // 1.上傳
   Boolean result = upload(file);
   if(result){
      return "上傳失敗";
   }
  UploadServiceImpl serviceImpl = SpringContextUtil.getBean(UploadServiceImpl.class)
  // 2.異步調用轉換圖片服務
  serviceImpl.transformToPictures()
  // 3.直接返回上傳結果
  return "上傳成功"; 
}

// 返回值修改爲void
@Async
public void transformToPictures(){

}

但是在springContextUtil.getBean()這裏直接報錯,說,找不到注入的bean,自己有點暈。暫且放置一邊,將代碼放到controller層運行,springContextUtil.getBean(MyController.class),結果發現,在controller層是可以異步調用的。那上面的問題怎麼回事?想了一會,serviceImpl是service接口的動態代理類,所以這裏的getBean應該放的是Service.class,這樣就獲得當前的代理類,但是此時調用異步方法是沒有的,所以要將異步方法加入到service接口中,然後在serviceImpl中重寫,再加入@Async註解即可。

3.代碼最終改爲這下面這樣

public class UploadServiceImpl implements UploadService{

// serviceImpl中部分方法
 @Override
 public String Upload(File file){

  // 1.上傳
   Boolean result = upload(file);
   if(result){
      return "上傳失敗";
   }
  UploadService service = SpringContextUtil.getBean(UploadService.class)
  // 2.異步調用轉換圖片服務
  service.transformToPictures()
  // 3.直接返回上傳結果
  return "上傳成功"; 
}

// 返回值修改爲void
@Async
@Override
public void transformToPictures(){

 }
}

這樣,上傳成功之後就會立刻返回成功結果,即使異步方法中拋出異常,也不會影響主方法的執行,前端用戶也是感知不到的

Tips:

  獲取當前類的代理對象除了上述的getBean()方法,還可以通過另一種方法獲得,如下:

在springboot項目的pom.xml文件中加入aop依賴

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>

在application.yml中開啓aop

aop:
    auto: true
    proxy-target-class: true

在上述代碼中獲得當前代理類地方通過Java的aop使用

UploadService uploadService = (UploadService) AopContext.currentProxy();

@Transactional註解也有類似的要注意的地方

當本類方法中沒有Transactional註解的A方法中調用帶有@Transactionalh註解的方法B,這是即使B中拋出異常,B方法也不會被回滾,要想生效,則需要獲得本類的代理類,再去調用,纔可以。但是如果不是本類,其他類調用,則可以正常回滾。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章