相关历史文章(阅读本文前,您可能需要先看下之前的系列)
国内最全的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 CallablecallableHandle() { 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 WebAsyncTaskwebAsyncTaskHandle(){ 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住当前的连接这种方式之外,你是否还有其它更好的方法呢?
另外你有什么话,想对悟纤说的,可以在评论区给小悟纤留言噢(*^▽^*)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)