SpringBoot使用线程池异步化解耦通知
1、前言
为什么我们需要使用WebSocket通信连接?在一般的HTTP协议中,当前端页面向后端发送一个请求后,会建立一条由客户端发起的通信连接,只能由客户端发起连接请求而服务端无法发起连接请求。
当我们在项目中集成了WebSocket通信连接之后,我们就可以建立起服务端与客户端之间的双向通信。
使用前提是需要在Application启动类中添加开启异步化通知
@EnableAsync // 开启异步化:另起一个线程执行后面内容
2、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
3、添加WebSocket配置类
主要是将WebSocket通信连接通过Bean方式注入到SpringBoot中
/**
* ClassName: WebSocket配置类
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
**/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3、创建WebSocket服务
/**
* ClassName: WebSocket服务端
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
*
* 开启一个WebSocket连接
*
**/
@Component
@ServerEndpoint("/ws/{token}")
public class WebSocketServer {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 每个客户端一个token
*/
private String token = "";
private static HashMap<String, Session> map = new HashMap<String, Session>();
/**
* 连接成功向map中加入key-value对
*
* @param token 键
* @param session 值
*/
@OnOpen
public void onOpen(@PathParam("token") String token, Session session){
map.put(token, session);
this.token = token;
logger.info("创建新连接:token:{},session id:{}, 当前连接数:{}",
token, session.getId(), map.size());
}
/**
* 连接关闭,删除session
*
* @param session
*/
@OnClose
public void onClose(Session session){
map.remove(this.token);
logger.info("连接已关闭:token:{},session id:{},剩余连接数:{}",
this.token, session.getId(), map.size());
}
/**
* 连接错误
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
logger.info("连接错误:", error);
}
/**
* 收到消息
*
* @param session 会话
* @param message 消息内容
*/
@OnMessage
public void onMessage(Session session, String message){
logger.info("收到消息:{},内容:{}", token, message);
}
/**
* 群发消息
*
* @param message
*/
public void sendInfo(String message){
for (String token : map.keySet()){
Session session = map.get(token);
try{
session.getBasicRemote().sendText(message);
}catch (IOException e){
logger.info("推送消息失败:{},内容:{}", token, message);
}
logger.info("推送消息:{},内容:{}", token, message);
}
}
}
4、配置线程池
/**
* ClassName: 异步化线程池配置
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
**/
@Configuration
public class ThreadPoolConfig {
/**
* 线程池
*
* @return
*/
@Bean(name = "threadPoolTaskExecutor")
public Executor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 配置线程池核心数量为5
executor.setCorePoolSize(5);
// 配置线程池最大数量为10
executor.setMaxPoolSize(10);
// 设置队列容量为200
executor.setQueueCapacity(200);
// 设置活动时间秒数为1分钟
executor.setKeepAliveSeconds(60);
// 设置线程名前缀
executor.setThreadNamePrefix("voteThreadPool-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
5、创建异步化解耦通知服务
在其他Service层中使用线程池进行异步化解耦通知时,必须将异步化方法单独定义到一个Service服务中,不能与调用异步化方法的服务在同一个类中,否则会无法使Async异步化生效。
通过@Async(value = “线程池名称”)来标注方法为异步化执行。
/**
* ClassName: WebSocket异步化解耦点赞通知服务
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
*
* 这里通过集成线程池来控制线程数量,但当业务量太大时还是会变为同步
* 最好的解决方案是使用rocketMQ消息队列来进行解决
*
*
**/
@Service
public class WsAsyncService {
private static final Logger logger = LoggerFactory.getLogger(WsAsyncService.class);
@Resource
private WebSocketServer webSocketServer;
/**
* 异步化解耦执行点赞通知发送消息
*
* @Async 标注该方法需要异步化执行(值为线程池)
* 单独另起一个线程执行,不干扰其他线程运行
* 使用该注解必须要单独写到另一个类中,否则无法实现异步化调用
*
* @param message 需要发送的消息字符串
*/
@Async(value = "threadPoolTaskExecutor")
public void sendInfo(String message){
logger.info("线程池执行:" + Thread.currentThread());
// 调用WebSocket服务端发送消息
webSocketServer.sendInfo(message);
}
}
6、调用异步化服务方法
将WebSocket服务注入到其他服务中,然后调用即可。
// 调用异步化推送点赞消息
wsAsyncService.sendInfo("【" + doc.getName() + "】被贵人赏识啦!");
7、前端Vue接收消息通知
<template>
<a-layout-footer style="text-align: center">
System of record ©2021 Created by {{userInfo.name}}
</a-layout-footer>
</template>
<!-- 添加组件命名规则-->
<script lang="ts">
import { defineComponent, onMounted, computed } from 'vue';
import store from "@/store";
import {Tool} from "@/util/tool";
import {notification} from "ant-design-vue"; // 引入通知组件
export default defineComponent({
name: 'the-footer',
setup() {
// 一个响应式变量要根据某个变量的变化来进行计算,用computed
const userInfo = computed(() => store.state.userInfo);
// 定义加载WebSocket连接
let websocket: any;
let token: any;
/**
* 定义WebSocket内部根据当前事件触发自动调用的方法
*/
const onOpen = () => {
console.log('WebSocket连接成功,状态码:', websocket.readyState);
};
const onError = () => {
console.log('WebSocket连接错误,状态码:', websocket.readyState);
};
const onClose = () => {
console.log('WebSocket连接关闭,状态码:', websocket.readyState);
};
const onMessage = (event: any) => {
console.log('WebSocket收到消息,状态码:', event.data);
// 接收到消息后弹出点赞内容
notification['success']({
message: '收到消息',
description: event.data
});
};
/**
* 初始化回调
*/
const initWebSocket = () => {
// 连接成功
websocket.onopen = onOpen;
// 手动消息
websocket.onmessage = onMessage;
// 连接错误
websocket.onerror = onError;
// 连接关闭
websocket.onclose = onClose;
};
onMounted(() => {
// websocket
if ('WebSocket' in window){
token = Tool.uuid(10);
// 连接地址
websocket = new WebSocket(process.env.VUE_APP_WS_SERVER + '/ws/' + token);
initWebSocket();
// 关闭
// websocket.close();
} else {
alert('当前浏览器不支持连接');
}
});
return{
userInfo
}
}
});
</script>