Spring Boot 异步任务入门

1.1引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-29-async-demo</artifactId>

    <dependencies>
        <!-- 引入 Spring Boot 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

1.2配置文件

spring:
  task:
    # Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。
    execution:
      thread-name-prefix: task- # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置
      pool: # 线程池相关
        core-size: 8 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。
        max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE
        keep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒
        queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。
        allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。
      shutdown:
        await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
        await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置

2.1Application

在启动类上添加 @EnableAsync 注解,启用异步功能,具体代码如下:

@SpringBootApplication
@EnableAsync // 开启 @Async 的支持
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.2 DemoService

// DemoService.java

@Async
public Future<Integer> execute01AsyncWithFuture() {
    return AsyncResult.forValue(this.execute01());
}

@Async
public Future<Integer> execute02AsyncWithFuture() {
    return AsyncResult.forValue(this.execute02());
}

2.3 DemoServiceTest测试类

// DemoServiceTest.java

@Test
public void task03() throws ExecutionException, InterruptedException {
    long now = System.currentTimeMillis();
    logger.info("[task03][开始执行]");

    // <1> 执行任务
    Future<Integer> execute01Result = demoService.execute01AsyncWithFuture();
    Future<Integer> execute02Result = demoService.execute02AsyncWithFuture();
    // <2> 阻塞等待结果
    execute01Result.get();
    execute02Result.get();

    logger.info("[task03][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);
}

运行单元测试,执行日志如下:

2019-11-30 16:10:22.226  INFO 69641 --- [           main] c.i.s.l.a.service.DemoServiceTest        : [task03][开始执行]

2019-11-30 16:10:22.272  INFO 69641 --- [         task-1] c.i.s.l.asynctask.service.DemoService    : [execute01]
2019-11-30 16:10:22.272  INFO 69641 --- [         task-2] c.i.s.l.asynctask.service.DemoService    : [execute02]

2019-11-30 16:10:32.276  INFO 69641 --- [           main] c.i.s.l.a.service.DemoServiceTest        : [task03][结束执行,消耗时长 10050 毫秒]

2.1异步回调

ListenableFuture接口
// ListenableFuture.java

public interface ListenableFuture<T> extends Future<T> {

    // 添加回调方法,统一处理成功和异常的情况。
    void addCallback(ListenableFutureCallback<? super T> callback);
    
    // 添加成功和失败的回调方法,分别处理成功和异常的情况。
    void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);
    
    // 将 ListenableFuture 转换成 JDK8 提供的 CompletableFuture 。
    // 这样,后续我们可以使用 ListenableFuture 来设置回调
    // 不了解 CompletableFuture 的胖友,可以看看 https://colobu.com/2016/02/29/Java-CompletableFuture/ 文章。
    default CompletableFuture<T> completable() {
    	CompletableFuture<T> completable = new DelegatingCompletableFuture<>(this);
    	addCallback(completable::complete, completable::completeExceptionally);
    	return completable;
    }
    
}

由上可见ListenableFuture接口继承了Future并提供了回调方法

2.2具体示例

DemoService 的代码,增加 execute02() 的异步调用,并返回 ListenableFuture 对象。代码如下:
// DemoService.java

@Async
public ListenableFuture<Integer> execute01AsyncWithListenableFuture() {
    try {
        return AsyncResult.forValue(this.execute02());
    } catch (Throwable ex) {
        return AsyncResult.forExecutionException(ex);
    }
}

根据执行的结果,包装出成功还是异常的 AsyncResult 对象。

DemoServiceTest 测试类,在塞等待执行完成的同时,添加相应的回调 Callback 方法。代码如下:
	// DemoServiceTest.java

@Test
public void task04() throws ExecutionException, InterruptedException {
   long now = System.currentTimeMillis();
   logger.info("[task04][开始执行]");

   // <1> 执行任务
   ListenableFuture<Integer> execute01Result = demoService.execute01AsyncWithListenableFuture();
   logger.info("[task04][execute01Result 的类型是:({})]",execute01Result.getClass().getSimpleName());
   execute01Result.addCallback(new SuccessCallback<Integer>() { // <2.1> 增加成功的回调

       @Override
       public void onSuccess(Integer result) {
           logger.info("[onSuccess][result: {}]", result);
       }

   }, new FailureCallback() { // <2.1> 增加失败的回调

       @Override
       public void onFailure(Throwable ex) {
           logger.info("[onFailure][发生异常]", ex);
       }

   });
   execute01Result.addCallback(new ListenableFutureCallback<Integer>() { // <2.2> 增加成功和失败的统一回调

       @Override
       public void onSuccess(Integer result) {
           logger.info("[onSuccess][result: {}]", result);
       }

       @Override
       public void onFailure(Throwable ex) {
           logger.info("[onFailure][发生异常]", ex);
       }

   });
   // <3> 阻塞等待结果
   execute01Result.get();

   logger.info("[task04][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);
}

3.1异步异常处理器

创建 GlobalAsyncExceptionHandler 类,全局统一的异步调用异常的处理器。代码如下:
	// GlobalAsyncExceptionHandler.java

@Component
public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        logger.error("[handleUncaughtException][method({}) params({}) 发生异常]",
                method, params, ex);
    }

}
创建 AsyncConfig 类,配置异常处理器。代码如下:
// AsyncConfig.java

@Configuration
@EnableAsync // 开启 @Async 的支持
public class AsyncConfig implements AsyncConfigurer {

    @Autowired
    private GlobalAsyncExceptionHandler exceptionHandler;

    @Override
    public Executor getAsyncExecutor() {
        return null;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return exceptionHandler;
    }

}
DemoService
	// DemoService.java

@Async
public Integer zhaoDaoNvPengYou(Integer a, Integer b) {
    throw new RuntimeException("测试异常处理器");
}
 DemoServiceTest
// DemoServiceTest.java

@Test
public void testZhaoDaoNvPengYou() throws InterruptedException {
    demoService.zhaoDaoNvPengYou(1, 2);

    // sleep 1 秒,保证异步调用的执行
    Thread.sleep(1000);
}
运行单元测试,执行日志如下:
2019-11-30 09:22:52.962 ERROR 86590 --- [         task-1] .i.s.l.a.c.a.GlobalAsyncExceptionHandler : [handleUncaughtException][method(public java.lang.Integer cn.iocoder.springboot.lab29.asynctask.service.DemoService.zhaoDaoNvPengYou(java.lang.Integer,java.lang.Integer)) params([1, 2]) 发生异常]

java.lang.RuntimeException: 程序员不需要女朋友
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值