如何在项目中使用Spring异步调用注解@Async
- 2019 年 10 月 25 日
- 筆記
本文主要介绍如何使用Spring框架提供的异步调用注解@Async,异步线程池配置、异常捕获处理。
开启@Async注解支持
使用@Async注解的之前,必须在项目中启动时调用@EnableAsync注解。比如通过定义一个JavaConfig文件:
@Configuration @EnableAsync public class AsyncConfig { }
异步调用
使用@Async异步执行无返回值的任务
定义一个任务类AsyncTask,包含两个执行耗时任务的方法task1()、task2(),在两个方法上添加@Async
@Service @Slf4j public class AsyncTask { @Async public void task1() { log.info("task1 start"); } @Async public void task2() { log.info("task2 start"); } }
定义测试类,串行调用AsyncTask.task1()和AsyncTask.task2()
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; @Test public void taskTest() { log.info("taskTest start"); asyncTask.task1(); asyncTask.task2(); log.info("taskTest end"); } }
从运行结果中看,task1和task2分别在两个不同的线程中执行:
INFO [15:18:29.182][main][com.breezek.demo.common.AsyncTaskTest][25]:taskTest start INFO [15:18:29.188][main][com.breezek.demo.common.AsyncTaskTest][29]:taskTest end INFO [15:18:29.192][task-1][com.breezek.demo.common.AsyncTask][29]:task2 start INFO [15:18:29.192][task-2][com.breezek.demo.common.AsyncTask][24]:task1 start
异步回调
使用@Async异步执行有返回值的任务,并获取任务执行结果。
定义AsyncTask类,创建两个带返回值的异步方法,返回值类型为Future
@Service @Slf4j public class AsyncTask { @Async public Future<String> task1() throws InterruptedException { log.info("task1 start"); Thread.sleep(5000L); log.info("task1 end"); return new AsyncResult<>("task1 result"); } @Async public Future<Integer> task2() throws InterruptedException { Integer abc = 1; log.info("task2 start"); Thread.sleep(10000L); log.info("task2 end"); return new AsyncResult<>(abc); } }
定义测试类,分别调用task1、task2,并等待task1和task2执行完毕
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class AsyncTaskTest { @Autowired private AsyncTask asyncTask; @Test public void taskTest() throws InterruptedException { log.info("taskTest start"); Future<String> task1Future = asyncTask.task1(); Future<Integer> task2Future = asyncTask.task2(); // do something for (int i = 0; i < 1000; i++) { } while (!task1Future.isDone() || !task2Future.isDone()) { } log.info("taskTest end"); } }
运行结果:
INFO [17:54:24.554][main][com.breezek.demo.common.AsyncTaskTest][28]:taskTest start INFO [17:54:24.566][task-1][com.breezek.demo.common.AsyncTask][27]:task1 start INFO [17:54:24.566][task-2][com.breezek.demo.common.AsyncTask][36]:task2 start INFO [17:54:29.569][task-1][com.breezek.demo.common.AsyncTask][29]:task1 end INFO [17:54:34.570][task-2][com.breezek.demo.common.AsyncTask][38]:task2 end INFO [17:54:34.570][main][com.breezek.demo.common.AsyncTaskTest][34]:taskTest end
可以看出来,main线程等待两个子线程执行完毕后再继续向下运行
使用@Async注解时,需要注意以下几点,否则异步调用不会生效:
- 异步方法不能定义为static类型
- 调用方法和异步方法不能定义在同一个类中
AsyncConfigurer配置
下面的代码中是如何配置异步调用使用的线程池、void返回值异常捕获处理
AsyncConfigurer接口是Spring提供的,我们定义JavaConfig时实现它:
@Configuration @EnableAsync @Slf4j public class AsyncConfig implements AsyncConfigurer { /** * 配置线程池,减少在调用每个异步方法时创建和销毁线程所需的时间 */ @Override public Executor getAsyncExecutor() { // 初始化Spring框架提供的线程池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(20); // 任务等待队列大小 executor.setQueueCapacity(10); // 任务拒绝策略,如果线程池拒绝接受任务,使用调用线程执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 定义线程名称前缀 executor.setThreadNamePrefix("async-executor-"); // 调用线程池初始化方法,如果在getAsyncExecutor()加上了@Bean注解,这个方法可以不调用,因为ThreadPoolTaskExecutor实现了InitializingBean接口,Spring在初始化Bean时会调用InitializingBean.afterPropertiesSet() executor.initialize(); return executor; } /** * void返回值异步方法异常捕获处理 */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler(); } /** * 异常捕获处理类 */ public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { log.error(String.format("Async method: %s has uncaught exception, params: %s.", method, JSON.toJSONString(params)), ex); } } }