Spring Boot使用Spring Callable和WebAsyncTask实现长轮询,战斗力杠杠的,这一节知识点满满的 - 第415篇

导读

         这一节我们主要来看看Spring+Callable、Spring WebAsyncTask以及Future+@Async如何来实现长轮询?

大家会看到,我这里的的描述中,有些是使用+的方式来进行表述,为什么这么表达呢?带着你的疑问,开启本文的阅读之旅吧。

                  长轮询系列:

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

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

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

(4)✅《Spring Boot使用Spring Callable和WebAsyncTask实现长轮询,战斗力杠杠的》

(5)✅《Spring Boot使用Future+@Async实现长轮询》

(6)「待定」《网友直呼:DeferredResult是Spring对Servlet异步处理的包装吗?》

这一节我们先来看看《Spring Boot使用Spring Callable和WebAsyncTask实现长轮询,战斗力杠杠的》以及《Spring Boot使用Future+@Async实现长轮询》

一、何为Callable和WebAsyncTask

1.1何为Callable?

面试官:线程的实现方式有几种?

面试者:2种,继承Thread类,实现Runnable接口。

         面试者正沉浸在自己以为完美的回答中的时候,这时面试官紧接着来了一个灵魂拷问:还有其它的实现方式吗?

         此时,面试者一脸懵逼了,纳尼,还有其它的方式吗?

         这就是我们要讲到的接口Callable。

         Callable和Runnable的区别是Callable可以有返回值,也可以抛出异常的特性,而Runnable没有。

         对于Callable的使用,我在很久很久很久以前(2020年1月7日),写过一篇文章:《我按摩你泡脚,你居然不等我「牛逼的Future」 - 第294篇》,大家看完这篇文章就知道Callable怎么使用了,文章地址:

https://mp.weixin.qq.com/s/Ztg7pwi2YFhQhYOaOhw5hw

         当然也可以关注公众号「SpringBoot」,,回复关键词「future」,进行文章的查看。

1.1.1 Callable使用的简单总结

对于Callable的具体使用,这里不重复说明,但总结重要的几点:

(1)接口Callable:Callable是一个接口,也就是具体的业务处理,如果只是实现了一个Callable,它自己并不能有啥效果,就如同Runnable还需要配合Thread进行启动。

(2)类FutureTask:对于Callable的数据如何获取呢,那么需要使用FutureTask来进行接收,在初始化FutureTask的时候,在构造方法将Callable传进去。

(3)Thread:最终如何启动呢?还是需要使用Thread进行启动,FutureTask也实现了接口Runnable,可以作为一个此参数传给Thread。

         说这么多,还不如代码看下代码明了:

         上面可能你有些代码可能看不懂,这是Lambda表达式对于代码的简写,JDK8的特性,还不懂的话,关注公众号「SpringBoot」,回复关键词「lambda」,查看Lambada表达式的系列文章:

《Java8新特性:Lambda表达式:小试牛刀》

《Java8新特性:Lambda表达式:过关斩将:使用场景》

《Java8新特性:Lambda表达式:摸摸里面》

1.1.2 Spring+Callable讲产生神奇的化学反应

         这里我们看到Callable(java.util.concurrent包下)是独立的存在,和Spring并没有太直接的关系,所以我们前面使用了+的方式,也就是说Spring+Callable将会产生神奇的化学反应,什么FutureTask、什么Thread在你可视范围将不复存在。(当然这是底层帮你处理了而已)

1.2何为WebAsyncTask?

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

         简单来说:WebAsyncTask类是Spring提供的一步任务处理类。

         另外要知道的一点就是:WebAsyncTask是Callable的升级版。

二、Spring+Callable

2.1 例子说明

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

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

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

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

(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 java.util.Date;import java.util.Random;import java.util.concurrent.*;/** * Spring+Callable * * @author 悟纤「公众号SpringBoot」 * @date 2022-01-13 * @slogan 大道至简 悟在天成 */@RestControllerpublic class SpringCallableController {    @GetMapping("/callableHandle")    public Callable<String> callableHandle() {        Callable<String> callable = new Callable<String>() {            @Override            public String call() throws Exception {                //执行耗时的逻辑                try {                    //休眠n秒钟进行模拟业务代码.                    TimeUnit.SECONDS.sleep(new Random().nextInt(7));                } catch (InterruptedException e) {                    e.printStackTrace();                }                return "love ~ "+new Date();            }        };        return callable;    }}

(1)定义了Callalbe的实现:由于Callable是一个接口,直接new,然后进行实现方法call,call的返回值在定义Callable的时候指定的,可以是任何类型。

(2)方法的返回值是Callable:到这里这是简单的一些基本的定义而已,真正的进行Callable的call的调用是在Spring MVC的逻辑里进行发起了,这里我们不需要关心。

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:"/callableHandle"                    ,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

         我们发现前面的代码的消息是定时出来的,如果处理耗时的时间的业务逻辑,倒是可以实现,目前咱们这里的需求是需要有一个地方进行发送消息,然后才能进行返回。所以我们需要稍微调整下,这里的调整可以参考前面的文章《SpringBoot使用Servlet居然也可以实现长轮询,敲了5年代码,我居然不知道》,既然Callable和Runnable一样,那么实现方式就就可以一样,这里不重复说明了。

三、Spring WebAsyncTask

         WebAsyncTask的使用和Callable基本上是一样的,我们只需要重新创建一个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.WebAsyncTask;import java.util.Date;import java.util.Random;import java.util.concurrent.Callable;import java.util.concurrent.TimeUnit;/** * WebAsyncTask实现长轮询 * * @author 悟纤「公众号SpringBoot」 * @date 2022-01-14 * @slogan 大道至简 悟在天成 */@RestControllerpublic class WebAsyncTaskController {    @GetMapping("/webAsyncTaskHandle")    public WebAsyncTask<String> webAsyncTaskHandle(){        long timeout = 4600;//超时时间.        /*            WebAsyncTask构建方法两个参数:timeout和Callable         */        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(timeout, new Callable<String>() {            //Callable.call();            @Override            public String call() throws Exception {                //执行耗时的逻辑                try {                    //休眠n秒钟进行模拟业务代码.                    TimeUnit.SECONDS.sleep(new Random().nextInt(7));                } catch (InterruptedException e) {                    e.printStackTrace();                }                return "love ~ "+new Date();            }        });        //(非必须设置)超时回调处理        webAsyncTask.onTimeout(new Callable<String>() {            @Override            public String call() throws Exception {                return "timeout";            }        });        //(非必须设置)错误回调处理        webAsyncTask.onError( ()-> "error" );        //(非必须设置)完成回调        webAsyncTask.onCompletion(new Runnable() {            @Override            public void run() {                System.out.println("complete...");            }        });        return webAsyncTask;    }}

         页面,只需要修改url为"/webAsyncTaskHandle"即可。

         使用http://127.0.0.1:8080/index发起请求:

四、Future+@Asyc

         这个具体的实现方式,也是在很久以前也实现过了,大家可以参考文章《Futurelove @Async的化学反应 - 第295篇》,关注公众号「SpringBoot」,回复关键词「future」。

五、长轮询方案小节

         最后使用了不少方案进行了长轮询的实现,这里要说的,方案不仅仅只有此。

         在这里简单的做个总结:

(1)Servlet3.0的AsyncContext。

(2)Spring DeferredResult。

(3)Spring+Future+Callable。

(4)Spring WebAsyncTask

(5)Future + @Async

5.1 区别

@Async、WebAsyncTask、Callable、DeferredResult的区别:

5.1.1 所在包不同

(1)@Async:org.springframework.scheduling.annotation;

(2)WebAsyncTask:org.springframework.web.context.request.async;

(3)Callable:java.util.concurrent;

(4)DeferredResult:org.springframework.web.context.request.async;

         @Async是位于scheduling包中,而WebAsyncTask和DeferredResult是用于Web(Spring MVC)的,而Callable是用于concurrent(并发)处理的。

5.1.2 关系

WebAsyncTask是对Callable的封装,提供了一些事件回调的处理,本质上区别不大。

         DeferredResult使用方式与Callable类似,重点在于跨线程之间的通信。

         @Async也是替换Runable的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller层,而可以是任何层的方法上。


购买完整视频,请前往:http://www.mark-to-win.com/TeacherV2.html?id=287