Springboot 实现WebSocket通信
引入WebSocket所需要starter模块依赖包;pom.xml文件引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
编写WebSocketConfig配置类
package com.gremlin.webSocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @className: WebSocketConfig
* @author: gremlin
* @version: 1.0.0
* @description: 引入自动配置类这个配置类ServerEndpointExporter,主要用于扫描WebSocket相关的注解
* @date: 2023/03/08 14:00
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
编写发送对象数据的编码器
package com.gremlin.webSocket;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.poi.ss.formula.functions.T;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
/**
* 发送object对象的编码器
* @author admin
*/
public class ServerEncoder implements Encoder.Text<Page<T>> {
@Override
public void destroy() {
}
@Override
public void init(EndpointConfig arg0) {
}
@Override
public String encode(Page<T> tPage) throws EncodeException {
try {
/*
* 这里是重点,只需要返回Object序列化后的json字符串就行
* 你也可以使用gosn,fastJson来序列化。
*/
JsonMapper jsonMapper = new JsonMapper();
return jsonMapper.writeValueAsString(tPage);
} catch ( JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
定义一个websocket类,注册到Spring容器中,使用@ServerEndpoint注解把当前类定义成一个服务器端并标记好客户端发起WebSocket连接请求的URL,暴露ws应用的路径
package com.gremlin.webSocket;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gremlin.service.NewsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @className: WebSocket
* @author: gremlin
* @version: 1.0.0
* @description: 注解@ServerEndpoint 暴露的ws应用的路径
* @date: 2023/03/08 14:03
*/
@Component
@Slf4j
@ServerEndpoint(value = "/websocket/{userId}", encoders = {
ServerEncoder.class})
public class WebSocket {
/**
* spring默认是单例的,而WebSocket是多对象的,也就是每次会产生不同的对象。
* 在初始化项目的时候,WebSocket就会产生一个对象,这时候就会注入service了,而当客户端与WebSocket服务端连接过后,
* 又会产生一个新对象,而spring默认是单例,只会给一个相同的对象注入一次service,
* 因此这时候的WebSocket新对象就不会再注入service了,
* 再去调用该service中的方法的话也就会发生空指针异常。
* 因此要写出静态 构造注入 注入的时候,给类的 service 注入
*/
private static NewsService newsService;
@Autowired
public void setNewsService (NewsService newsService) {
WebSocket.newsService= newsService;
}
/**
* 用来存储服务连接对象
*/
private static Map<String,WebSocket> clientMap = new ConcurrentHashMap<>();
/**
* @Open:当WebSocket连接建立成功后会触发这个注解修饰的方法;
* @Close:当WebSocket连接关闭或中断后会触发这个注解修饰的方法;
* @OnMessage:当客户端发送消息到服务端时,会触发这个注解修饰的方法;
* @OnError:当 websocket 建立连接时出现异常会触发这个注解修饰的方法;
*/
private Session session;
private String userId;
/**
* 客户端与服务端连接成功
*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) throws IOException {
this.session = session;
this.userId = userId;
// 与当前客户端连接成功时
if (clientMap.containsKey(userId)){
clientMap.remove(userId);
clientMap.put(userId,this);
} else{
clientMap.put(userId,this);
}
// sendAllMessage("连接成功","连接成功");
// 连接成功则立刻发送内容
sendInfo("连接成功",userId);
log.info("-----------------连接成功---------------: " + userId);
}
/**
* 客户端与服务端连接关闭
*/
@OnClose
public void onClose(Session session){
log.info("连接关闭:"+userId);
// 与当前客户端连接关闭时
clientMap.remove(userId);
}
/**
* 客户端向服务端发送消息
*/
@OnMessage
public void onMsg(Session session,String message) throws IOException {
// 收到来自当前客户端的消息时
log.info("客户端向服务端发送消息: "+ userId);
clientMap.get(userId).sendAllMessage(message,"");
}
/**
* 向客户端发送消息
*/
private void sendAllMessage(String message,Object data) throws IOException {
log.info("向客户端发送消息:"+userId);
try {
this.session.getBasicRemote().sendObject(data);
log.info("服务端向客户端发送消息成功!");
} catch (IOException | EncodeException e) {
e.printStackTrace();
}
}
/**
* 发送自定义消息
* */
public void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
log.info("发送消息到: " + userId + ",message: " + message);
if(StringUtils.isNotBlank(userId) && clientMap.containsKey(userId)){
// 此处调用接口获取需要发送的数据,此处的data可以是集合也可以是Page等
clientMap.get(userId).sendAllMessage(message,data);
}else{
log.error("发送消息错误,接收用户不在线");
}
}
/**
* 客户端与服务端连接异常
*/
@OnError
public void onError(Throwable error,Session session) {
log.info("客户端与服务端连接异常:"+userId);
error.printStackTrace();
}
}