作者:丁仪

来源:https://chengxuzhixin.com/blog/post/SpringMVC-yi-bu-chu-li-de-5-zhong-fang-shi.html

前段时间研究了下 diamond 的原理,其中有个重要的知识点是长连接的实现,用到了 servlet 的异步处理。异步处理最大的好处是可以提高并发量,不阻塞当前线程。其实 Spring MVC 也支持了异步处理,本文记录下相关的技术点。

异步处理 demo

如果要启用异步返回,需要开启 @EnableAsync。如下的代码中,使用 DeferredResult 进行异步处理。

请求进来后,首先创建 DeferredResult 对象,设置超时时间为 60 秒。然后指定 DeferredResult 在异步完成和等待超时时的回调。同步的处理只需要创建异步任何,然后返回 DeferredResult 即可。这样 Spring MVC 处理完此次请求后,不会立即返回 response 给客户端,会一直等待 DeferredResult 处理完成。如果 DeferredResult 没有在 60 秒内处理完成,就会触发超时,然后返回 response 给客户端。

@RequestMapping(value = "/async/demo")
public DeferredResult<String> async(){
// 创建 DeferredResult,设置超时时间 60s
DeferredResult<String> deferredResult = new DeferredResult<>((long)60 * 1000); String uuid = UUID.randomUUID().toString();
Runnable callback = () -> manager.remove(deferredResult, uuid);
// 设置完成和超时的回调
deferredResult.onCompletion(callback);
deferredResult.onTimeout(callback); // 创建异步任务
manager.addAsyncTask(deferredResult, uuid); // 同步返回 DeferredResult
return deferredResult;
}

对于异步任务来说,需要持有 DeferredResult 对象。在异步处理结束时,需要手动调用 DeferredResult.setResult 完成输出。调用 setResult 时,数据输出写到客户端,然后触发异步完成事件执行回调。

task.getDeferredResult().setResult(ConfigJsonUtils.toJsonString(map));

使用 DeferredResult 进行异步处理

DeferredResult 这个类代表延迟结果。DeferredResult 可以用在异步任务中,其他线程能够获取 DeferredResult 并设置 DeferredResult 的返回数据。通常可以使用线程池、队列等配合 DeferredResult 实现异步处理。

根据官方描述,Spring MVC 处理流程如下:

  1. 把 controller 返回的 DeferredResult 保存在内存队列或集合当中;
  2. Spring MVC 调用 request.startAsync(),开启异步;
  3. DispatcherServlet 和所有的 Filter 退出当前请求线程;
  4. 业务应用在异步线程中设置 DeferredResult 的返回值,Spring MVC 会再次发送请求;
  5. DispatcherServlet 再次被调用,并使用 DeferredResult 的返回值;

使用 Callable 进行异步处理

使用 Callable 进行异步处理与 DeferredResult 类似。不同的是,Callable 会交给系统指定的 TaskExecutor 执行。

根据官方描述,Spring MVC 处理流程如下:

  1. controller 返回 Callable;
  2. Spring MVC 调用 request.startAsync(),开启异步,提交 Callable 到一个任务线程池;
  3. DispatcherServlet 和所有的 Filter 退出当前请求线程;
  4. 业务应用在异步线程中返回值,Spring MVC 会再次发送请求;
  5. DispatcherServlet 再次被调用,并使用 Callable 的返回值;
@RequestMapping(value = "/async/demo")
public Callable<String> async(){
Callable<String> callable = () -> String.valueOf(System.currentTimeMillis());
// 同步返回
return callable;
}

使用 ListenableFuture 进行异步处理

ListenableFuture 作为返回值,与 DeferredResult 类似。也需要使用者自行处理异步线程,但不支持超时、完成回调,需要自行处理。

@RequestMapping(value = "/async/demo")
public ListenableFuture<String> async(){
ListenableFutureTask<String> ListenableFuture= new ListenableFutureTask<>(() -> {
return String.valueOf(System.currentTimeMillis());
});
Executors.newSingleThreadExecutor().submit(ListenableFuture);
return ListenableFuture;
}

使用 ResponseBodyEmitter 进行异步处理

DeferredResult 和 Callable 都只能返回一个异步值。如果需要返回多个对象,就要使用 ResponseBodyEmitter。返回的每个对象都会被 HttpMessageConverter 处理并写回输出流。如果希望设置更多返回数据,如 header、status 等,可以把 ResponseBodyEmitter 作为 ResponseEntity 的实体数据返回。

@RequestMapping("/async/responseBodyEmitter")
public ResponseBodyEmitter responseBodyEmitter(){
ResponseBodyEmitter responseBodyEmitter=new ResponseBodyEmitter(); Executors.newSingleThreadExecutor().submit(() -> {
try {
responseBodyEmitter.send("demo");
responseBodyEmitter.send("test");
responseBodyEmitter.complete();
} catch (Exception ignore) {}
}); return responseBodyEmitter;
}

使用 StreamingResponseBody 进行异步处理

如果希望跳过返回值的自动转换,直接把输出流写入 OutputStream,可以使用 StreamingResponseBody。也可以作为 ResponseEntity 的实体数据返回。

@RequestMapping("/async/streamingResponseBody")
public StreamingResponseBody streamingResponseBody(){
StreamingResponseBody streamingResponseBody = outputStream -> {
Executors.newSingleThreadExecutor().submit(() -> {
try {
outputStream.write("<html>streamingResponseBody</html>".getBytes());
} catch (IOException ignore) {}
});
};
return streamingResponseBody;
}

各种处理方式的对比

以上几种异步处理方式各有差异,需要按需取舍。对比如下。

可返回次数

数据转换

回调

线程池

DeferredResult

1 次

完成、超时

自行处理

Callable

1 次

系统处理

ListenableFuture

1 次

自行处理

ResponseBodyEmitter

多次

自行处理

StreamingResponseBody

多次

自行处理

推荐阅读

Linux Cron 定时任务

人类简史、软件架构和中台

限流算法探秘

Git 工作原理

MyBatis 一级二级和自定义缓存

SpringMVC异步处理的 5 种方式的更多相关文章

  1. SpringMVC 返回json的两种方式

    前后台数据交互使用json是一种很重要的方式.本文主要探讨SpringMVC框架使用json传输的技术. 请注意,本文所提到的项目使用Spring 版本是4.1.7,其他版本在具体使用上可能有不一样的 ...

  2. springMVC返回数据的四种方式

    转自:https://blog.csdn.net/itcats_cn/article/details/82119673 springMVC返回数据的四种方式:第一种,通过request.setAttr ...

  3. SpringMVC实现文件下载的两种方式及多文件下载

    1.传统方法 @RequestMapping("/download") public String download( String fileName ,String filePa ...

  4. SpringMVC获取参数的几种方式

    前言: 年末了,忙了一年了却发现系统的整理的东西很少,一些基础的东西都未做整理,这里就将它随便整理一下,增加一些印象,当然在网上看到一些好的资料也会整理下来以备后用.今天整理一下springMVC获取 ...

  5. Java 异步编程的几种方式

    前言 异步编程是让程序并发运行的一种手段.它允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失 ...

  6. 【整理】Ajax异步实现的几种方式总结

    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术.GET ...

  7. 总结ASP.NET MVC视图页使用jQuery传递异步数据的几种方式

    在ASP.NET MVC的视图页向控制器传递异步数据,可能是数组,JavaScript对象,json,表单数据,等等. 关于数据,JavaScript对象有时候和json长得一模一样,有么有? var ...

  8. springmvc配置AOP的两种方式

    spingmvc配置AOP有两种方式,一种是利用注解的方式配置,另一种是XML配置实现. 应用注解的方式配置: 先在maven中引入AOP用到的依赖 <dependency> <gr ...

  9. JavaScript处理异步请求的几种方式(取异步函数返回值)

    JavaScript处理异步的几种方式 Javascript语言的执行环境是"单线程"(single thread,就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个 ...

随机推荐

  1. 分组背包 例题:hdu 1712 ACboy needs your help

    分组背包需求 有N件物品,告诉你这N件物品的重量以及价值,将这些物品划分为K组,每组中的物品互相冲突,最多选一件,求解将哪些物品装入背包可使这些物品的费用综合不超过背包的容量,且价值总和最大. 解题模 ...

  2. spoj687 REPEATS - Repeats (后缀数组+rmq)

    A string s is called an (k,l)-repeat if s is obtained by concatenating k>=1 times some seed strin ...

  3. 迪杰斯特拉+拆点 Deliver the Cake - HDU 6805

    题意: t组输入,给你n个点m条边.你需要输出从s点到t点的最短距离,然后是m条边,每条边输入信息为: a,b,c 表示从a点到b点的一个无向边长度为c 每一个点会有一个属性L.R或M 如果a和b一个 ...

  4. 1006 How many?

    Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 36   Accepted: 2 Description 有一天,小Q给了小J ...

  5. DSC注册Agent失败- InternalServerError

    问题 有大概5台Agent Server,注册的时候,发现2台可以成功,其他的不成功. 注册失败的错误日志如下: 初步尝试 首先,Pull Server已经平稳的运行了几年了,此次注册还有部分Agen ...

  6. 国产网络测试仪MiniSMB - 如何配置VLAN数据流

    国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...

  7. kubernetes生态--交付prometheus监控及grafana炫酷dashboard到k8s集群

    由于docker容器的特殊性,传统的zabbix无法对k8s集群内的docker状态进行监控,所以需要使用prometheus来进行监控: 什么是Prometheus? Prometheus是由Sou ...

  8. 使用 HTML5 & CSS 实现一个自定义开关按钮 switch button

    使用 HTML5 & CSS 实现一个自定义开关按钮 switch button switch button <div class="switch"> < ...

  9. node --experimental-modules & node.js ES Modules

    node --experimental-modules & node.js ES Modules how to run esm modules in node.js cli $ node -v ...

  10. Docker In Action

    Docker In Action Docker 实战 https://docs.docker.com/get-started/overview/ Docker Engine Docker Archit ...