Spring Boot使用Spring DeferredResult实现长轮询纵享新丝滑让你体验丝滑般的感觉 - 第414篇

原创 悟纤 SpringBoot 2022-01-13 08:30

关历史文章(阅读本文前,您可能需要先看下之前的系列👇

国内最全的Spring Boot系列之四

享元模式:共享女友 - 第355篇

SpringBoot 使用validation数据校验之国际化问题怎么搞?满满的干货,值得收藏 - 第411篇

什么是轮询、长轮询、长连接一篇文章让你不在懵懂 - 第412篇

Spring Boot使用Servlet居然也可以实现长轮询,敲了5年代码,我居然不知道 - 第413篇

师傅:徒儿,醒醒,太阳已经晒到屁股了。

图片

悟纤:师傅,不知最近为何徒儿竟如此之困。

图片

师傅:难道是冬天到了?

悟纤:也不知为何,看来徒儿的除了学习也得多运动运动了。

师傅:动动更健康。

图片

悟纤:师傅找我这是要教我新知识了?

师傅:还记得前几天为师教你的servlet异步任务实现长轮训吧。今天为师教你使用Spring DeferredResult来进行实现。

悟纤:好耶ヾ(✿゚▽゚)ノ,师傅咱们开始吧,我已经迫不及待想学习了。

图片

导读

         Spring 3.2开始引入了DeferredResult,有助于将长时间运行的计算从http-worker线程卸载到单独的线程。

         这一节我们将来一探Spring DeferredResult,看看它究竟是何方神圣。

         长轮询系列:

(1)✅《什么是轮询、长轮询、长连接一篇文章让你不在懵懂

(2)✅《Spring Boot使用Servlet居然也可以实现长轮询

(3)✅《Spring Boot使用Spring DeferredResult实现长轮询,纵享新丝滑让你体验丝滑般的感觉》

(4)「待拟定」《Spring Boot使用Spring Callable实现长轮询》

(5)「待拟定」…

这一节我们先来看看《Spring Boot使用Spring DeferredResult实现长轮询,纵享新丝滑让你体验丝滑般的感觉》。

一、初识Spring DeferredResult

1.1 DeferredResult是什么?

         从Spring 3.2开始引入了DeferredResult,有助于将长时间运行的计算从http-worker线程卸载到单独的线程。

尽管另一个线程将占用一些资源来进行计算,但同时不会阻止工作线程,并且可以处理传入的客户端请求。

         DeferredResult:deferred(推迟、延缓)、result(结果),延迟结果,这是一个异步处理类,使用DeferredResult能够实现非阻塞的REST。

1.2 DeferredResult如何使用

         使用起来很简单,只需要new一个DeferredResult对象即可,然后进行返回,如下:

图片

         客户端请求映射到控制器方法返回值为DeferredResult时,会立即释放Tomcat线程并将请求挂起,直到调用setResult()方法或者超时,才会响应客户端请求。

         所以我们可以把复杂的代码另起一个线程进行处理,处理结果使用deferredResult.setResult()进行设置,至此请求相应给前端,请求结束。

二、深入Spring DeferredResult

2.1 例子说明

         接下里我们会使用一个小栗子来演示使用DeferredResult的异步处理来实现长轮询。

         对于这个例子先总体的说明下:

(1)有一个页面会使用ajax定时的请求后台,5秒一请求,看是否有新的信息发布。

(2)后端接收到请求之后会使用DeferredResult的异步处理请求,如果此时没有新的信息的话,那么等待超时。

(3)打开新的一个窗口,调用发布新的消息的请求发布新消息。

(4)此时ajax定时请求的页面,应该会及时的显示新的信息。

2.2 环境说明

(1)OS:Mac OS

(2)开发工具:IntelliJ Idea

(3)JDK:1.8

(4)Spring Boot:2.6.1

2.3 开发步骤

(1)构建一个基本的Spring Boot框架

(2)构建一个发布请求的Controller

(3)构建一个页面定时请求后台的Controller

(4)启动测试

2.4 开发实战

2.4.1构建一个基本的Spring Boot框架

         使用开发工具构建一个基本的Spring Boot项目,这一步没啥好说的,

2.4.2构建一个发布请求的Controller

         我们先看下Controller的代码,然后再解释核心部分的代码:

package com.kfit.springbootlongpollingdemo.test;
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.context.request.async.DeferredResult;
import java.util.Date;import java.util.Random;import java.util.concurrent.TimeUnit;
/** * Spring DeferredResult * * @author 悟纤「公众号SpringBoot」 * @date 2022-01-12 * @slogan 大道至简 悟在天成 */@RestControllerpublic class SpringDeferredResultController {
    @GetMapping("/handleReqDefResult")    public DeferredResult<String> handleReqDefResult(){        long timeoutValue = 4700;//超时时间.        DeferredResult<String> deferredResult = new DeferredResult<>(timeoutValue);
        new Thread(){            @Override            public void run() {                //执行耗时的逻辑                try {                    //休眠n秒钟进行模拟业务代码.                     TimeUnit.SECONDS.sleep(new Random().nextInt(7));                } catch (InterruptedException e) {                    e.printStackTrace();                }
                //返回结果.                deferredResult.setResult("love ~ "+new Date());            }        }.start();
        return deferredResult;    }}

说明:

(1)使用DeferredResult定义了一个超时时间。

(2)这里的返回值是DeferredResult类型。

(3)开启了一个线程模拟业务的复杂流程,当处理完成之后使用deferredResult.setResult("love ~ "+new Date()); 返回数据,这里是模拟了一个数据,并不是用户进行设置的,先来个简单版本,待会再使用优化。

3.4.3构建一个页面定时请求后台的Servlet

         看下index.html的代码:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>长轮询</title></head><body>
    <b>长轮询小栗子</b>    <div id="message"></div>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>    <script>
        $(function () {
            function getMessage() {                $.ajax({                    //  /longPollingServlet                    url:"/handleReqDefResult"                    ,data:{}                    ,type:"get"                    ,timeout:5000 //定义超时时间为5秒                    ,success:function(rs){                        if(rs !=''){                            $("#message").append("<p>"+rs+"</p>");                        }                    }                    ,complete:function(rs){                        console.log("重新发起");                        getMessage();                    }                });            }
            getMessage();        });</script>
</body></html>

说明:

(1)使用了jquery的ajax请求后台请求。

(2)对于长轮询前端做了什么呢?其一就是请求返回之后再次发起请求以此hold连接;其二就是定义了一个超时时间timeout,超时之后也会再次发起请求。这里不管是请求成功了还是超时了,jquery的ajax都会执行complete方法。

3.4.4启动测试

         启动应用,然后访问地址:

http://127.0.0.1:8080/index

图片

         我们可以看到会定时的往控制台进行输出信息。

         另外我们可以看到如果超时了,会看到超时的请求:

图片

2.5 开发实战优化

         我们发现前面的代码的消息是定时出来的,如果处理耗时的时间的业务逻辑,倒是可以实现,目前咱们这里的需求是需要有一个地方进行发送消息,然后才能进行返回。所以我们需要稍微调整下,这里就有的讲究了:

public static Map<HttpServletRequest,DeferredResult<String>> requestMap = new ConcurrentHashMap<>();@GetMapping("/handleReqDefResult")public DeferredResult<String> handleReqDefResult(HttpServletRequest req){    long timeoutValue = 5000;//超时时间.    DeferredResult<String> deferredResult = new DeferredResult<>(timeoutValue);    deferredResult.onTimeout(()->{        //超时移除元素.        requestMap.remove(req);        System.out.println("当前map元素个数:"+requestMap.size());    });    requestMap.put(req,deferredResult);    return deferredResult;}

说明:

(1)定义一个Map存放DeferredResult,这些类我们可以在别的地方进行使用。

(2)利用DeferredResult的超时方法onTimeout处理超时的DeferredResult。

(3)在实际中,这里的Map里的key可以是用户的id,也可以是订单的id,比如要实现支付支付成功回调通知的业务需求就可以保存订单的id。

         那么定义一个请求进行信息变动的发起:

@RequestMapping({"/publishMsg1"})@ResponseBodypublic String publishMsg1(String message){    if(SpringDeferredResultController.requestMap.size()>0){        for(Map.Entry<HttpServletRequest,DeferredResult<String>> entry:SpringDeferredResultController.requestMap.entrySet()){            entry.getValue().setResult(message);        }    }    return "OK";}

         这时候在页面在发起请求:

http://127.0.0.1:8080/index

         界面上没有任何信息,需要我们发布一个信息,才能进行显示:

http://127.0.0.1:8080/publishMsg1?message=love1

         在查看页面:

图片

悟纤小结

(1)DeferredResult的使用特别简单,声明一个变量,然后返回DeferredResult:

图片

(2)客户端请求映射到控制器方法返回值为DeferredResult时,会立即释放Tomcat线程并将请求挂起,直到调用setResult()方法或者超时,才会响应客户端请求。

我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。

à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

ShardingJDBC分库分表:http://t.cn/A6ZarrqS

分布式事务解决方案:http://t.cn/A6ZaBnIr

JVM内存模型调优实战:http://t.cn/A6wWMVqG

Spring入门到精通:https://t.cn/A6bFcDh4

大话设计模式之爱你:https://dwz.cn/wqO0MAy7

SpringBoot117

java12

SpringBoot · 目录

上一篇Spring Boot使用Servlet居然也可以实现长轮询,敲了5年代码,我居然不知道 - 第413篇下一篇Spring Boot使用Callable和WebAsyncTask实现长轮询,战斗力杠杠的,这一节知识点满满的 - 第415篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值