跳到主要内容
JackSparrow414
返回

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和Fucurel类型,所以上面的代码返回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注解也有类似的要注意的地方

Java 业务方法调用同类事务方法,注释说明需要通过代理调用才会触发事务

和

Java 方法上配置 @Transactional(rollbackFor = Exception.class) 的示例

当本类方法中没有Transactional注解的A方法中调用带有@Transactionalh注解的方法B,这是即使B中抛出异常,B方法也不会被回滚,要想生效,则需要获得本类的代理类,再去调用,才可以。但是如果不是本类,其他类调用,则可以正常回滚。


分享本文:

上一篇
shiro使用(最终集成springboot代码合集)
下一篇
关于MySQL多表关联时过滤条件的位置

评论

欢迎提问、纠错和分享经验。发表评论需要登录 GitHub;中英文版本共享评论。

评论仅在正式网站开放。