SpringMVC添加异步请求支持

  • WebAsyncTask
使用场景:由于java web项目主线程可以处理的线程数有限,当请求量过大,主线程就会阻塞,所以需要后台接收到请求的时候需要启动副线程去完成业务逻辑的处理,主线程直接返回,这样主线程占用的时间很短,可以继续处理下一个请求

使用场景

使用方法:将SpringMVC的controller的方法的返回值封装成WebAsyncTask,并设置超时时间,和超时处理方法

使用案例

@RequestMapping(value = "/cheStopWithoutAuth")
    @ResponseBody
    public WebAsyncTask<AjaxResponse> cheStop(String port,
                                          AjaxResponse ar) throws Exception {
        log.info("主线程开始");
        Callable<AjaxResponse> result = (() -> {
            log.info("副线程开始");
            cheService.cheStop(port);
            ar.setSuccessMessage(ResultEnums.SUCCESS.getMessage(), null);
            log.info("副线程结束");
            return ar;
        });  //直接Callable也可以,但是不能设置超时时间和超时回调以及成功回调
        WebAsyncTask<AjaxResponse> webAsyncTask = new WebAsyncTask<>(300000, result);
        webAsyncTask.onTimeout(timeOutCallBack());
        log.info("主线程结束");
        return webAsyncTask;
    }

/**
* 超时回调
*/
private Callable<AjaxResponse> timeOutCallBack(){

        Callable<AjaxResponse> callback = (()-> {
                log.info("请求超时");
                AjaxResponse ajaxResponse = new AjaxResponse();
                ajaxResponse.setErrorMessage("请求超时",null);
                return ajaxResponse;
        });
        return callback;
    }

注意web.xml应用需在所有的servlet和filter配置加上<async-supported>true</async-supported>

filter放置位置

servlet放置位置

  • DeferredResult

使用场景:当遇到主线程直接启动副线程去处理请求逻辑的时候,WebAsyncTask可以满足,但是如果遇到一些比较复杂的情况,比如当前web应用接收到下单请求会把请求放在一个异步处理消息队列里,然后由应用2去处理队列里的请求,应用2处理完把结果返回应用1,然后应用1监听消息队列的返回结果并返回给前端,这个时候WebAsyncTask就无能为力了,这时可以使用DeferredResult

使用场景

使用方法:每次接收到请求,主线程即把请求放在消息队列中,然后创建一个DeferredResult,并将请求的唯一键与创建的DeferredResult封装到异步请求处理器里,然后注册监听器,监听消息队列的完成事件,当监听到完成事件事,从map中取出对应的DeferredResult并返回

使用案例

1、模拟消息队列
@Data
@Slf4j
@Component
public class MockQueue {
    private String placeOrder;   //下单消息
    private String completeOrder;  //订单完成的消息

    public void setPlaceOrder(String placeOrder) throws InterruptedException {
        new Thread(()->{
            log.info("接单下单请求");
            try {
                Thread.sleep(1000);  //模拟应用2处理下单的过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;  //给completeOrder设值代表下单请求处理完毕
            log.info("下单请求处理完毕:{}",placeOrder);
        }).start();

    }

    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}
2、模拟异步请求处理器
@Component
@Data
public class DeferredResultHolder {

    //key:订单号  value:订单的处理结果
    private Map<String,DeferredResult<String>> map = new HashMap<>();
}
3、注册监听器监听请求处理完成事件
/**
 * ContextRefreshedEvent 整个容器初始化完毕的一个事件
 */
@Component
@Slf4j
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private MockQueue mockQueue;
    @Autowired
    private DeferredResultHolder deferredResultHolder;


    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        new Thread(() ->{
            while (true){
                if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())){  //当订单完成的字段有值,说明有完成的订单
                    String orderNumber = mockQueue.getCompleteOrder();
                    log.info("返回订单处理结果:"+orderNumber);
                    deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
                    mockQueue.setCompleteOrder(null);
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
4、主线程组织调用
 @RequestMapping("/order2")
    public DeferredResult<String> order2() throws InterruptedException {
        log.info("主线程开始");
        String orderNumber = RandomStringUtils.randomNumeric(8);  //生成8位订单号

        mockQueue.setPlaceOrder(orderNumber);

        DeferredResult<String> result = new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNumber,result);

        log.info("主线程返回");
        return result;
    }

注意SprinbBoot中对异步请求注册过滤器应继承WebMvcConfigurerAdapter并重写configureAsyncSupport方法

image.png

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Netkiller

PHP 安全与性能

PHP 安全与性能 摘要 我的系列文档 Netkiller Architect 手札Netkiller Developer 手札Netkiller PHP 手札...

4896
来自专栏Fish

安装IDEA和运行SCALA程序

下载与配置IDEA 从官网下载 里面有Ultimate(最终版)和Community(社区版),对于普通的开发者来说,社区版就够了,然后因为我本来配置了JDK...

3779
来自专栏LinkedBear的个人空间

运用Oltu框架搭建OAuth的Demo工程 转

http://jinnianshilongnian.iteye.com/blog/2038646

1214
来自专栏云原生架构实践

JHipster生成微服务架构的应用栈(二)- 认证微服务示例

这里选择JHipster UAA server,这是一种基于OAuth认证机制的微服务。

2384
来自专栏乐百川的学习频道

Intellij IDEA 2017.3 基于编辑器的REST客户端介绍

最近Intellij IDEA更新到了2017.3这一版本,这个版本又增加了很多新功能。我觉得其中这个基于编辑器的REST客户端这个功能很不错,可以为我们带来很...

2418
来自专栏云计算教程系列

如何在Ubuntu上安装Ruby和Sinatra

如果选择几个词来定义Sinatra,那么肯定会是设计简洁,给人启发。这种项目构建的想法引领了很多项目的构建潮流——跨越不同的编程语言和平台的编程方案。

1264
来自专栏运维咖啡吧

LDAP落地实战(一):OpenLDAP部署及管理维护

上边来了一堆的名词解释,看的云里雾里,还不是很明白,怎么跟自己的组织架构对应起来呢?看看下边的图是不是清晰明了

3663
来自专栏zhisheng

SpringBoot Kafka 整合使用

前提 假设你了解过 SpringBoot 和 Kafka。 1、SpringBoot 如果对 SpringBoot 不了解的话,建议去看看 DD 大佬 和 纯洁...

88616
来自专栏cmazxiaoma的架构师之路

蛋疼的ElasticSearch(三)之配置elasticsearch-analysis-ik和集群

1.下载https://github.com/medcl/elasticsearch-analysis-ik

5013
来自专栏程序猿DD

Spring Boot中使用RabbitMQ

复刻一篇老文,为后续要发的内容做一些铺垫 Message Broker与AMQP简介 Message Broker是一种消息验证、传输、路由的架构模式,其设计目...

27710

扫码关注云+社区

领取腾讯云代金券