上文介绍了基于 @Async 注解的 异步调用编程,本文将继续引入 Spring Boot 的 WebAsyncTask 进行更灵活异步任务处理,包括 异步回调,超时处理 和 异常处理。

正文

1. 处理线程和异步线程

在开始下面的讲解之前,在这里先区别下两个概念:

处理线程:处理线程 属于 web 服务器线程,负责 处理用户请求,采用 线程池 管理。

异步线程:异步线程 属于 用户自定义的线程,可采用 线程池管理。

Spring 提供了对 异步任务 API,采用 WebAsyncTask 类即可实现 异步任务。对异步任务设置相应的 回调处理,如当 任务超时、异常抛出 等。异步任务通常非常实用,比如:当一笔订单支付完成之后,开启异步任务查询订单的支付结果。

2. 环境准备

配置gradle依赖

利用 Spring Initializer 创建一个 gradle 项目 spring-boot-web-async-task,创建时添加相关依赖。得到的初始 build.gradle 如下:

buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
} apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management' group = 'io.ostenant.springboot.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8 repositories {
mavenCentral()
} dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

配置服务类

配置一个用于异步任务调度的 Mock 服务。

@Service
public class WebAsyncService {
public String generateUUID() {
return UUID.randomUUID().toString();
}
}

配置异步处理控制器并注入以上服务 Bean

@RestController
public class WebAsyncController {
private final WebAsyncService asyncService;
private final static String ERROR_MESSAGE = "Task error";
private final static String TIME_MESSAGE = "Task timeout"; @Autowired
public WebAsyncController(WebAsyncService asyncService) {
this.asyncService = asyncService;
}
}

3. 正常异步任务

配置一个正常的 WebAsyncTask 任务对象,设置任务 超时时间 为 10s。异步任务执行采用 Thread.sleep(long) 模拟,这里设置 异步线程 睡眠时间为 5s

@GetMapping("/completion")
public WebAsyncTask<String> asyncTaskCompletion() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>( * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep( * 1000L);
return asyncService.generateUUID();
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
out.println("继续处理其他事情");
return asyncTask;
}
@GetMapping("/completion")
public WebAsyncTask<String> asyncTaskCompletion() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>( * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep( * 1000L);
return asyncService.generateUUID();
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
out.println("继续处理其他事情");
return asyncTask;
}

启动 Spring Boot 项目,访问 http://localhost:8080/completion ,发起 正常 的异步任务请求。

观察控制台输出,可以验证 WebAsyncTask 的异步处理流程正常

请求处理线程:http-nio--exec-
继续处理其他事情
异步工作线程:MvcAsync1
任务执行完成

Web 页面正常响应,页面响应消息如下:

注意:WebAsyncTask.onCompletion(Runnable) :在当前任务执行结束以后,无论是执行成功还是异常中止,onCompletion的回调最终都会被调用。

4. 抛出异常异步任务

配置一个 错误 的 WebAsyncTask 任务对象,设置任务 超时时间 为 10s。在异步任务执行方法中 抛出异常。

@GetMapping("/exception")
public WebAsyncTask<String> asyncTaskException() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>( * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep( * 1000L);
throw new Exception(ERROR_MESSAGE);
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onError(() -> {
out.println("任务执行异常");
return ERROR_MESSAGE;
}); out.println("继续处理其他事情");
return asyncTask;
}
@GetMapping("/exception")
public WebAsyncTask<String> asyncTaskException() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>( * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep( * 1000L);
throw new Exception(ERROR_MESSAGE);
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onError(() -> {
out.println("任务执行异常");
return ERROR_MESSAGE;
}); out.println("继续处理其他事情");
return asyncTask;
}

启动 Spring Boot 项目,访问 http://localhost:8080/exception ,发起 异常 的异步任务请求。

Web 页面响应异常信息如下:

观察控制台输出,可以验证 WebAsyncTask 对于 异常请求 的异步处理过程。

请求处理线程:http-nio--exec-
继续处理其他事情
异步工作线程:MvcAsync2
-- ::10.110 ERROR --- [nio--exec-] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception java.lang.Exception: Task error
at io.ostenant.springboot.sample.controller.WebAsyncController.lambda$asyncTaskException$(WebAsyncController.java:) ~[classes/:na]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$(WebAsyncManager.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:) ~[na:1.8.0_172]
at java.util.concurrent.FutureTask.run(FutureTask.java:) ~[na:1.8.0_172]
at java.lang.Thread.run(Thread.java:) [na:1.8.0_172] -- ::10.111 ERROR --- [nio--exec-] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.Exception: Task error] with root cause java.lang.Exception: Task error
at io.ostenant.springboot.sample.controller.WebAsyncController.lambda$asyncTaskException$(WebAsyncController.java:) ~[classes/:na]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$(WebAsyncManager.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:) ~[na:1.8.0_172]
at java.util.concurrent.FutureTask.run(FutureTask.java:) ~[na:1.8.0_172]
at java.lang.Thread.run(Thread.java:) [na:1.8.0_172] 任务执行异常
-- ::10.144 WARN --- [nio--exec-] o.apache.catalina.core.AsyncContextImpl : onError() failed for listener of type [org.apache.catalina.core.AsyncListenerWrapper] java.lang.IllegalArgumentException: Cannot dispatch without an AsyncContext
at org.springframework.util.Assert.notNull(Assert.java:) ~[spring-core-5.0..RELEASE.jar:5.0..RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.dispatch(StandardServletAsyncWebRequest.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.setConcurrentResultAndDispatch(WebAsyncManager.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$(WebAsyncManager.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$(StandardServletAsyncWebRequest.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at java.util.ArrayList.forEach(ArrayList.java:) ~[na:1.8.0_172]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onError(StandardServletAsyncWebRequest.java:) ~[spring-web-5.0..RELEASE.jar:5.0..RELEASE]
at org.apache.catalina.core.AsyncListenerWrapper.fireOnError(AsyncListenerWrapper.java:) ~[tomcat-embed-core-8.5..jar:8.5.]
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:) ~[tomcat-embed-core-8.5..jar:8.5.]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:) [tomcat-embed-core-8.5..jar:8.5.]
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:) [tomcat-embed-core-8.5..jar:8.5.]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:) [tomcat-embed-core-8.5..jar:8.5.]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:) [tomcat-embed-core-8.5..jar:8.5.]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:) [tomcat-embed-core-8.5..jar:8.5.]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:) [tomcat-embed-core-8.5..jar:8.5.]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:) [na:1.8.0_172]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:) [na:1.8.0_172]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:) [tomcat-embed-core-8.5..jar:8.5.]
at java.lang.Thread.run(Thread.java:) [na:1.8.0_172] 任务执行完成
注意:WebAsyncTask.onError(Callable

5. 超时异步任务

配置一个正常的 WebAsyncTask 任务对象,设置任务 超时时间 为 10s。异步任务执行采用 Thread.sleep(long) 模拟,这里设置 异步线程 睡眠时间为 15s,引发异步任务超时。

@GetMapping("/timeout")
public WebAsyncTask<String> asyncTaskTimeout() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>( * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep( * 1000L);
return TIME_MESSAGE;
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onTimeout(() -> {
out.println("任务执行超时");
return TIME_MESSAGE;
}); out.println("继续处理其他事情");
return asyncTask;
}

启动 Spring Boot 项目,访问 http://localhost:8080/timeout ,发起 超时 的异步任务请求。

观察控制台输出,可以验证 WebAsyncTask 的异步超时处理的过程。

请求处理线程:http-nio--exec-
继续处理其他事情
异步工作线程:MvcAsync3
任务执行超时
任务执行完成

Web 页面常响应超时提示信息,页面响应消息如下:

注意:WebAsyncTask.onTimeout(Callable

6. 线程池异步任务
上面的三种情况中的 异步任务 默认不是采用 线程池机制 进行管理的。

也就是说,一个请求进来,虽然释放了处理线程,但是系统依旧会为每个请求创建一个 异步任务线程,也就是上面看到的 MvcAsync 开头的 异步任务线程。

后果就是开销严重,所以通常采用 线程池 进行统一的管理,直接在 WebAsyncTask 类构造器传入一个 ThreadPoolTaskExecutor 对象实例即可。

构造一个线程池 Bean 对象:

@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize();
taskExecutor.setMaxPoolSize();
taskExecutor.setQueueCapacity();
taskExecutor.setThreadNamePrefix("asyncTask");
return taskExecutor;
}
}

在控制器中注入 ThreadPoolTaskExecutor 对象,重新配置基于 线程池 的 异步任务处理。

@Autowired
@Qualifier("taskExecutor")
private ThreadPoolTaskExecutor executor; @GetMapping("/threadPool")
public WebAsyncTask<String> asyncTaskThreadPool() {
return new WebAsyncTask<>( * 1000L, executor,
() -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
return asyncService.generateUUID();
});
}

并发地请求 http://localhost:8080/threadPool ,观察控制台输出的 异步线程 信息,可以发现 异步任务 直接从 线程池 中获取 异步线程。

异步工作线程:asyncTask1
异步工作线程:asyncTask2
异步工作线程:asyncTask3
异步工作线程:asyncTask4
异步工作线程:asyncTask5
异步工作线程:asyncTask1
异步工作线程:asyncTask2
异步工作线程:asyncTask3
异步工作线程:asyncTask4
异步工作线程:asyncTask5

小结

本文介绍了 Spring Boot 提供的 WebAsyncTask 的异步编程 API。相比上问介绍的 @Async 注解,WebAsyncTask 提供更加健全的 超时处理 和 异常处理 支持。

												

Spring boot 使用WebAsyncTask处理异步任务的更多相关文章

  1. Spring Boot使用@Async实现异步调用

    原文:http://blog.csdn.net/a286352250/article/details/53157822 项目GitHub地址 : https://github.com/FrameRes ...

  2. Spring boot 实现高吞吐量异步处理(适用于高并发场景)

    技术要点 org.springframework.web.context.request.async.DeferredResult<T> 示例如下: 1.   新建Maven项目  asy ...

  3. Spring Boot任务(定时,异步,邮件)

    一.定时任务 开启定时任务(在Spring Boot项目主程序上添加如下注解) @EnableScheduling //开启定时任务的注解 创建定时任务(创建一个Service如下) @Service ...

  4. spring boot:使用log4j2做异步日志打印(spring boot 2.3.1)

    一,为什么要使用log4j2?     log4j2是log4j的升级版,     升级后更有优势:     性能更强/吞吐量大/支持异步     功能扩展/支持插件/支持自定义级别等     这些优 ...

  5. Spring Boot使用@Async实现异步调用:自定义线程池

    前面的章节中,我们介绍了使用@Async注解来实现异步调用,但是,对于这些异步执行的控制是我们保障自身应用健康的基本技能.本文我们就来学习一下,如果通过自定义线程池的方式来控制异步调用的并发. 定义线 ...

  6. Spring Boot 异步请求和异步调用,一文搞定

    一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...

  7. (转)spring boot注解 --@EnableAsync 异步调用

    原文:http://www.cnblogs.com/azhqiang/p/5609615.html EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. @Co ...

  8. spring boot注解 --@EnableAsync 异步调用

    EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. @Component public class Task { @Async public void doT ...

  9. spring boot / cloud (四) 自定义线程池以及异步处理@Async

    spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...

随机推荐

  1. linux内核修炼之道

    华清远见·任桥伟   人民邮电 2010 内核不学,岂能理解?今天开始正式学习内核原理 linux 发行版本Mint. cat  /etc/issue     # sudo lsb_release - ...

  2. Struts2和SpringMVC的action是单例还是原型的?

    struts2的acion单独使用的时候应是多例的,也就是原型(prototype). 因为它是基于类开发的,它的三种获取页面传参的方式都是通过成员变量的方式来接受的. 如果用struts2框架基于方 ...

  3. dockerfile封装docker镜像

    一.使用都dockerfile封装docker镜像 1.在任意一个地方创建文件夹docker mkdir docker 2.进入文件夹并且下载一个django2.1.7的包以及一个epel.repo ...

  4. python 反射和内置方法

    一.isinstance和issubclass class Foo: pass class Son(Foo): pass s = Son() #判断一个对象是不是这个类的对象,传两个参数(对象,类) ...

  5. cloudera-scm-server启动时出现Caused by: javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Could not open connection问题解决方法(图文详解)

    问题现象 查看 [root@cmbigdata1 cloudera-scm-server]# pwd /var/log/cloudera-scm-server [root@cmbigdata1 clo ...

  6. 使用自动化恶意软件分析cuckoo分析exe程序

    Cuckoo是一款监控函数调用,监控文件读写,监控注册表读写等的自动化恶意分析软件. 包括apk.exe.pdf等多种文件的分析,将任务提交给数据库,host从数据库中读取任务,将样本放入虚拟机中运行 ...

  7. solr6.6教程-基础环境搭建(一)

    目前网上关于solr6.+的安装教程很少,有些6.0之前的教程在应用到6.+的版本中出现很多的问题,所以特别整理出来这一片文章,希望能给各位码农一些帮助! 很少写些文章,如有不对的地方,还希望多多指导 ...

  8. .netCore2.0 过滤器

    不同的过滤器类型会在执行管道的不同阶段运行,因此他们各自有一套自己的应用场景.可以根据不同的业务需求和在请求管道中的执行位置来选择合适创建的过滤器.运行与MVC Action调用管道内的过滤器有时候被 ...

  9. wcf 登录认证 angular 认证重定向

    自定义认证管理器,分为两级:1.登陆认证.2.权限认证.权限主要是用户.角色.角色用户关系.功能(系统资源).角色功能关系,5部分决定用户的权限(视图). 两层认证都通过后,更新session的最新交 ...

  10. Change - Why we need coding standards

    Change - Why we need coding standards I have the idea of coding standards when I have to review my t ...