使用Callable实现springmvc的异步请求

如果一个请求中的某些操作耗时很长,会一直占用线程。这样的请求多了,可能造成线程池被占满,新请求无法执行的情况。这时,可以考虑使用异步请求,即主线程只返回Callable类型,然后去处理新请求,耗时长的业务逻辑由其他线程执行。

下面是一个示例demo,用线程睡眠来模拟耗时操作,springmvc配置以及视图解析器、拦截器等组件的注册略,详见https://www.cnblogs.com/dubhlinn/p/10808879.html博文,本文只展示controller组件,欢迎页面welcome.jsp略。

package cn.monolog.annabelle.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.concurrent.Callable; /**
* 处理器,用于测试springmvc异步请求
* created on 2019-05-12
*/
@Controller
@RequestMapping(value = "/asyn")
public class AsynController { /**
* 使用Callable发送异步请求进入欢迎页面
* @return
*/
@RequestMapping(value = "/welcome")
public Callable<String> welcome() {
//打印主线程
System.out.println("主线程" + Thread.currentThread().getName() + "开始:" + System.currentTimeMillis()); //使用使用Callable获取页面路径
Callable<String> path = new Callable() {
@Override
public String call() throws Exception {
//打印副线程
System.out.println("副线程" + Thread.currentThread().getName() + "开始:" + System.currentTimeMillis());
//线程休眠5秒钟
Thread.sleep(5000);
//打印副线程
System.out.println("副线程" + Thread.currentThread().getName() + "结束:" + System.currentTimeMillis());
//返回欢迎页面的url
return "welcome";
}
}; //打印主线程
System.out.println("主线程" + Thread.currentThread().getName() + "结束:" + System.currentTimeMillis()); //返回页面
return path;
} }

在浏览器访问/asyn/welcome,会延时5秒钟才进入欢迎页面,然后来看服务器日志,发现是这样的:

1. 主线程开始、结束几乎是同一时刻;

2. 副线程结束比开始晚了5秒多,是在执行线程休眠,即模拟"耗时多的业务逻辑";

3. 拦截器的preHandle方法执行了两次。

1和2都符合我们之前的描述和预期,但是为什么拦截器的preHandle方法会执行两次?来看一下springmvc异步请求的原理:

1. 当处理器的方法返回Callable(以及其他异步形式的返回值,例如DeferredResult)时,springmvc会将

Callable的实现方法交给TaskExecutor在一个隔离的线程中执行;

2. 同时,DispatcherServlet(前端控制器)和所有的拦截器退出web容器的线程,但是response仍然保持打开状态;

3. 当Callable的实现方法产生返回值时,springmvc会将请求重新派发给容器;

4. DispatcherServlet(前端控制器)重新接收请求,并根据Callable的返回值进行视图渲染或者返回数据。

因此,第一次请求之前执行了preHandle方法,当前端控制器接收到请求之后,发现返回值是Callable,

拦截器就退出主线程了,也就没有了后面的postHandle和afterCompletion方法。

使用DeferredResult实现springmvc的异步请求

实际项目中的异步请求,可能在不同的接口中执行,甚至可能在不同的应用中执行,这时Callable就无法实现。

springmvc提供了另一种异步返回类型:DeferredResult,其主要使用步骤是:

1. 在主线程中新建DeferredResult实例,然后直接返回这个实例,主线程结束;

2. 副线程在主线程新建的DeferredResult实例中设置值(调用setResult方法);

3. 这时,主线程获取到副线程在DeferredResult实例中设置的值,重新接收请求、视图渲染或返回数据。

下面是一个示例demo,我们用一个自定义队列来模拟DeferredResult的存取。

自定义队列

package cn.monolog.annabelle.springmvc.queue;

import org.springframework.web.context.request.async.DeferredResult;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; /**
* 自定义队列,用于存储DeferredResult
* created on 2019-05-12
*/
public class DeferredResultQueue { //队列
private static Queue<DeferredResult> deferredResultQueue = new ConcurrentLinkedQueue<>(); /**
* 添加DeferredResult进队列
* @param deferredResult
*/
public static void save(DeferredResult deferredResult) {
deferredResultQueue.add(deferredResult);
} /**
* 从队列中取出第一个元素并删除
* @return
*/
public static DeferredResult get() {
return deferredResultQueue.poll();
}
}

controller组件

package cn.monolog.annabelle.springmvc.controller;

import cn.monolog.annabelle.springmvc.queue.DeferredResultQueue;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult; import java.util.UUID; /**
* 处理器,用于测试springmvc异步请求
* created on 2019-05-12
*/
@Controller
@RequestMapping(value = "/asyn")
public class AsynController { /**
* 异步创建订单
* 只返回DeferredResult,并不真正创建订单
* @return
*/
@RequestMapping("/createOrder")
@ResponseBody
public DeferredResult<String> createOrder() {
//新建DeferredResult实例,并保存到队列中,参数的意义是最长等待5秒,否则返回值自动设置为timeout
DeferredResult<String> deferredResult = new DeferredResult<>((long)5000, "timeout");
DeferredResultQueue.save(deferredResult);
//返回
return deferredResult;
} /**
* 真正的创建订单
* @return
*/
@RequestMapping("/create")
@ResponseBody
public String createOrderActioin() {
//随机生成订单号
String orderNum = UUID.randomUUID().toString();
//从队列中取出DeferredResult
DeferredResult deferredResult = DeferredResultQueue.get();
//向DeferredResult中存值
deferredResult.setResult(orderNum);
//返回
return orderNum;
}
}

直接在浏览器访问/asyn/createOrder,因为并没有线程为DeferredResult存值,因此等待5秒之后,返回timeout。

如果在5秒之内,在浏览器的另一个标签访问/asyn/create,会发现第一个标签返回了第二个接口创建的订单编号。

使用Callable或DeferredResult实现springmvc的异步请求的更多相关文章

  1. 15.SpringMVC之异步请求

    SpringMVC中异步请求相关组件 SpringMVC在此基础上对异步请求进行了封装.提供了AsyncWebRequest类型的request,并提供了处理异步请求的管理器WebAsyncManag ...

  2. springmvc webservlet 异步请求总结

    1:每次请求会启动一个新线程 上边在debug状态下, 每次请求一次,生成一个新的 thread  在此已经是245了 出现一个现象在debug模式下, 每次请求生成的线程,自动在红框那个位置停了下来 ...

  3. springmvc 配置异步请求

    最开始按照网上配置了一个servlet class 没有继承Filter .结果报错.网上有文章说是tomcat 启动加载的servlet-3.0- api 加载了 tomcat 安装目录下lib里边 ...

  4. SpringMVC异步调用,Callable和DeferredResult的使用

    Callable和DeferredResult都是springMVC里面的异步调用,Callable主要用来处理一些简单的逻辑,DeferredResult主要用于处理一些复杂逻辑 1.Callabl ...

  5. springmvc的异步处理

         关于异步的好处我在这里就不多说了,自从servlet3.1规范发布以来,控制层的异步处理也越来越多的被人提及.而Spring5的webflux诞生也意味着Spring全方位对异步提供了支持. ...

  6. 天天写同步,5种SpringMvc异步请求了解下!

    引言 说到异步大家肯定首先会先想到同步.我们先来看看什么是同步? 所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作. 简单来说,同步就是必须一件一件事做,等前一件 ...

  7. SpringMVC DeferedResult和servlet3.1 AsyncContext异步请求

    先看一个简单的示例: @RequestMapping("/getFuture") public Future<String> getFuture() { System. ...

  8. Spring MVC 异步请求 Callable

    对于有的请求业务处理流程可能比较耗时,比如长查询,远程调用等,主线程会被一直占用,而tomcat线程池线程有限,处理量就会下降 servlet3.0以后提供了对异步处理的支持,springmvc封装了 ...

  9. Springmvc中 同步/异步请求参数的传递以及数据的返回

    转载:http://blog.csdn.net/qh_java/article/details/44802287 注意: 这里的返回就是返回到jsp页面 **** controller接收前台数据的方 ...

随机推荐

  1. git ssh 绑定 GitLab

    入职新公司之后,需要使用GitLab,可是我不会啊,又不想麻烦运维大佬,所以自己找乐一下,发现网上都是些很陈旧的教程,所以准备自己记录下来 第一步 设置Git端上的用户名和用户邮箱: 假如入你已经安装 ...

  2. 32-第3章 数据链路层--抓包分析数据帧格式-ISO一图了然-小结

    OSI理论模型 层级 名称 事物举例 功能 数据单位 别名 数据组成 协议举例 7 应用层 QQ.OA 网络通信 上层数据 上层数据 HTTP/FTP/DNS 6 表示层 web数据压缩.https加 ...

  3. VMware三种连接方式bridge, nat, host-only

    大家在安装完虚拟机后,默认安装了两个虚拟网卡,VMnet1和 VMnet8,其他的未安装(当然也可以手动安装其他的).其中VMnet1是host网卡,用于host方式连接网络的.VMnet8是NAT网 ...

  4. 接口开发中 遇到的坑——Java byte与C# byte 数据转换问题

    前提: 公司与其他公司进行接口对接 需要使用Byte[]数据流传输数据 原本想法如下:直接IO生成就ok了 using System; using System.IO; using System.Te ...

  5. - Power Strings (字符串哈希) (KMP)

    https://www.cnblogs.com/widsom/p/8058358.htm (详细解释) //#include<bits/stdc++.h> #include<vect ...

  6. AUC计算方法

    本质是ROC曲线下的面积,ROC曲线x轴是误判率/误报率(false positive rate),y轴是准确率/命中率(true positive rate). AUC是ROC曲线与横轴所围的面积. ...

  7. 隐马尔可夫模型的前向算法(java实现),今天奉上

    隐马尔可夫模型的前向算法(手动实现),今天奉上,由于研究生期间,实现的时候没有多加注释,这里为了让更好的人进入自然语言处理领域,特此,将前向算法奉上,具体公式可参考52nlp的HMN系列博客. 参考了 ...

  8. java动态代理(JDK和cglib)(转载)

    原文地址:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html 高亮部分是我的理解. JAVA的动态代理 代理模式 代理模式是常用的j ...

  9. Lengauer-Tarjan算法的相关证明

    Lengauer-Tarjan算法的相关证明 0. 约定 为简单起见,下文中的路径均指简单路径(事实上非简单路径不会对结论造成影响). \(V\)代表图的点集,\(E\)代表图的边集,\(T\)代表图 ...

  10. jmeter测试文件上传功能

    最近为了完成自动化KPI开始慢慢接触jmeter,其中遇到了不少问题,今天就遇到了文件上传的问题,在这里记录下加深记忆,也可供jmeter初级使用者作为一个参考.另外论坛上已有同事发过关于jmeter ...