Spring Boot - 异步任务

如题所述

第1个回答  2022-07-15

有时候,前端可能提交了一个耗时任务,如果后端接收到请求后,直接执行该耗时任务,那么前端需要等待很久一段时间才能接受到响应。如果该耗时任务是通过浏览器直接进行请求,那么浏览器页面会一直处于转圈等待状态。一个简单的例子如下所示:

当我们在浏览器请求 localhost:8080/async/ 页面时,可以看到浏览器一直处于转圈等待状态,这样体验十分不友好。

事实上,当后端要处理一个耗时任务时,通常都会将耗时任务提交到一个异步任务中进行执行,此时前端提交耗时任务后,就可直接返回,进行其他操作。

在 Java 中,开启异步任务最常用的方式就是开辟线程执行异步任务,如下所示:

这时浏览器请求 localhost:8080/async/ ,就可以很快得到响应,并且耗时任务会在后台得到执行。

一般来说,前端不会关注耗时任务结果,因此前端只需负责提交该任务给到后端即可。但是如果前端需要获取耗时任务结果,则可通过 Future 等方式将结果返回,详细内容请参考后文。

事实上,在 Spring Boot 中,我们不需要手动创建线程异步执行耗时任务,因为 Spring 框架已提供了相关异步任务执行解决方案,本文主要介绍下在 Spring Boot 中执行异步任务的相关内容。

Spring 3.0 时提供了一个 @Async 注解,该注解用于标记要进行异步执行的方法,当在其他线程调用被 @Async 注解的方法时,就会开启一个线程执行该方法。

: @Async 注解通常用在方法上,但是也可以用作类型上,当类被 @Async 注解时,表示该类中所有的方法都是异步执行的。

在 Spring Boot 中,如果要执行一个异步任务,只需进行如下两步操作:

被 @Async 注解的异步任务方法存在相关限制:

默认情况下,Spring 会自动搜索相关线程池定义:要么是一个唯一 TaskExecutor Bean 实例,要么是一个名称为 taskExecutor 的 Executor Bean 实例。如果这两个 Bean 实例都不存在,就会使用 SimpleAsyncTaskExecutor 来异步执行被 @Async 注解的方法。

综上,可以知道,默认情况下,Spring 使用的 Executor 是 SimpleAsyncTaskExecutor , SimpleAsyncTaskExecutor 每次调用都会创建一个新的线程,不会重用之前的线程。很多时候,这种实现方式不符合我们的业务场景,因此通常我们都会自定义一个 Executor 来替换 SimpleAsyncTaskExecutor 。

对于自定义 Executor(自定义线程池),可以分为如下两个层级:

前文介绍过,对于被 @Async 注解的异步方法,只能返回 void 或者 Future 类型。对于返回 Future 类型数据,如果异步任务方法抛出异常,则很容易进行处理,因为 Future.get() 会重新抛出该异常,我们只需对其进行捕获即可。但是对于返回 void 的异步任务方法,异常不会传播到被调用者线程,因此我们需要自定义一个额外的异步任务异常处理器,捕获异步任务方法抛出的异常。

自定义异步任务异常处理器的步骤如下所示: