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 大道至简 悟在天成
*/
@RestController
public 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 大道至简 悟在天成
*/
@RestController
public 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