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

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

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

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

国内最全的Spring Boot系列之四

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

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

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

Spring Boot使用Servlet居然也可以实现长轮询,敲了5年代码,我居然不知道 - 第413篇Spring Boot使用Spring DeferredResult实现长轮询纵享新丝滑让你体验丝滑般的感觉 - 第414篇

悟纤:师傅,上一节讲的SpringDeferredResult真的是太好用了,瞬间感觉代码清爽了很多。

师傅:那是,为师是谁?为师可是前无古人后无来者的存在。

悟纤:师傅,你这是要“飘“了吧。

师傅:让为师幻想幻想,开心一下也不行吗。

悟纤:那师傅,你好好幻想噢,最好是沉浸在其中,不能自拔最好了。

师傅:你这嘴… 是不是有点… 算了,为师大人有大量,不和你计较了。今天为师心情好,就在和你讲讲实现长轮询的3种方案。

悟纤:(吃惊)3种方案,师傅你怎么突然一下子要讲这么多种方案了,我怕消化不了。

师傅:徒儿莫慌,为师这么安排主要是其中的2种方案有异曲同工之妙,放在一起理解会更好理解;另外的一种方案呐,为师在很久很久很久以前和你讲过的,只是你可能已经忘记了,但没有关系,为师还记着。

悟纤:徒儿,已经迫不及待的想学习来着了,咱们赶紧开始吧,小伙伴估计和我一样内心已经蠢蠢欲动了…

导读

         这一节我们主要来看看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.*;​@RestControllerpublic class SpringCallableController {​    @GetMapping("/callableHandle")    public Callable callableHandle() {        Callable callable = new Callable() {            @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的代码:

        长轮询​    长轮询小栗子    ​        ​​

说明:

(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;​@RestControllerpublic class WebAsyncTaskController {​    @GetMapping("/webAsyncTaskHandle")    public WebAsyncTask webAsyncTaskHandle(){        long timeout = 4600;//超时时间.​                WebAsyncTask webAsyncTask = new WebAsyncTask<>(timeout, new Callable() {            //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() {            @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层,而可以是任何层的方法上。

悟纤小结

(1)Servlet3.0提供了AsyncContext支持异步处理。

(2)Spring DeferredResult在AsyncContext进行了优化,实现了更简单的异步的实现。

(3)Callable是并发编程提供的支持有返回值的异步处理方式。

(4)WebAsyncTask在Callable的基础上进行了包装,提供了更强大的功能,比如:处理超时回调、错误回调、完成回调等。

(5)@Async提供了更优为Runnable的实现方式。

         至于在实际的代码中,我们可能还需要借助其它的类配合实现以此来达到更好的效果。

有网友觉得轮询不过瘾,要使用长连接进行实现,也可以学习课程WebSocket系列:

《从零开始学Spring Boot Plus》:http://t.cn/A6ZagYTi

         最后留下几个小问题供大家进行思考:

(1)Callable和WebAsyncTask怎么修改为有消息才推送消息的方式?

(2)对于后端的处理代码,为什么要指定超时时间?

(3)对于后端使用while(true)的方式进行hold住当前的连接这种方式之外,你是否还有其它更好的方法呢?

         另外你有什么话,想对悟纤说的,可以在评论区给小悟纤留言噢(*^▽^*)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5707005.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存