场景描述:我司最近要做一个上传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方法也不会被回滚,要想生效,则需要获得本类的代理类,再去调用,才可以。但是如果不是本类,其他类调用,则可以正常回滚。