在 Spring Boot 中集成 WebSocket 可以通过 spring-boot-starter-websocket 依赖实现。WebSocket 允许服务器和客户端进行全双工通信,适用于聊天系统、实时通知等应用场景。
-
添加依赖
在 pom.xml 文件中添加 WebSocket 相关的 Spring Boot 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
-
配置 WebSocket
在 Spring Boot 中,需要创建一个 WebSocket 配置类来启用 WebSocket 功能。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/ws")
.setAllowedOrigins("*"); // 允许所有来源,生产环境需指定域名
}
}
@EnableWebSocket:启用 WebSocket 支持。registerWebSocketHandlers():注册 WebSocket 处理器。setAllowedOrigins("*"):允许跨域访问,生产环境应限制来源。
-
编写 WebSocket 处理器
创建一个 MyWebSocketHandler,用于处理 WebSocket 连接、消息接收和关闭事件。
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
public class MyWebSocketHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("新连接建立: " + session.getId());
session.sendMessage(new TextMessage("欢迎连接 WebSocket 服务器!"));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息: " + message.getPayload());
session.sendMessage(new TextMessage("服务器收到: " + message.getPayload()));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接关闭: " + session.getId());
}
}
afterConnectionEstablished():客户端连接建立时调用,可发送欢迎消息。handleTextMessage():接收客户端发送的文本消息,并返回相同的内容。afterConnectionClosed():连接关闭时执行清理操作。
-
前端测试 WebSocket
使用 JavaScript 在浏览器中测试 WebSocket:
const socket = new WebSocket("ws://localhost:8080/ws");
socket.onopen = function () {
console.log("WebSocket 连接已打开");
socket.send("Hello Server!");
};
socket.onmessage = function (event) {
console.log("收到服务器消息: " + event.data);
};
socket.onclose = function () {
console.log("WebSocket 连接已关闭");
};
-
运行和测试
- 启动 Spring Boot 应用,确保 WebSocket 监听
/ws路径。 - 在浏览器控制台执行 JavaScript 代码,查看 WebSocket 通信效果。
-
使用
@ServerEndpoint实现 WebSocket(基于javax.websocket)
如果需要基于 @ServerEndpoint 方式,可以使用 Spring Boot 提供的 spring-boot-starter-websocket,但需要引入 javax.websocket-api。
-
添加依赖
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
-
WebSocket 服务器端
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/ws")
public class WebSocketServer {
private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSockets.add(this);
System.out.println("新连接加入,总连接数: " + webSockets.size());
}
@OnMessage
public void onMessage(String message) throws IOException {
System.out.println("收到消息: " + message);
sendMessage("服务器响应: " + message);
}
@OnClose
public void onClose() {
webSockets.remove(this);
System.out.println("连接关闭,总连接数: " + webSockets.size());
}
@OnError
public void onError(Session session, Throwable error) {
System.err.println("WebSocket 错误: " + error.getMessage());
}
public void sendMessage(String message) throws IOException {
for (WebSocketServer ws : webSockets) {
ws.session.getBasicRemote().sendText(message);
}
}
}
-
启用
@ServerEndpoint
由于 Spring Boot 默认不支持 @ServerEndpoint,需要手动注册:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.websocket.server.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 总结
| 方式 | 说明 | 适用场景 |
|---|---|---|
| WebSocketHandler(推荐) | 使用 WebSocketHandler 和 WebSocketConfigurer 配置 | 适用于 Spring Boot 原生支持 |
| @ServerEndpoint(基于 Javax) | 直接在类上使用 @ServerEndpoint 注解 | 适用于 Java EE 标准 WebSocket 方式 |
如果你的 Spring Boot 项目是Spring WebFlux,则可以使用 @EnableWebSocket 配合 reactor-netty 进行 WebSocket 处理。
你更倾向于哪种方式?
-
业务中实践
在实际业务中,WebSocket 通常用于 实时通信 场景,例如:
- 在线聊天(单聊、群聊)
- 消息推送(系统通知、警报)
- 实时数据更新(股票、天气、订单状态)
- 协同编辑(在线文档、多人游戏)
接下来,我会基于 WebSocketHandler 方式,举例说明如何在 Spring Boot 业务中应用 WebSocket。
-
需求:实现实时消息推送
假设你有一个电商后台系统,需要给用户推送订单状态更新消息,WebSocket 服务器负责:
- 客户端(用户)连接 WebSocket,并提供
userId进行身份验证。 - 服务器端存储用户的 WebSocket 连接,并在有订单状态更新时推送消息。
- 断开连接时,清理 WebSocket 连接。
- 代码实现
-
维护 WebSocket 连接
创建一个 WebSocketSessionManager,用于存储在线用户的 WebSocket 连接,并提供消息推送能力。
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketSessionManager {
// 存储 WebSocket 连接,key 为 userId
private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
// 添加用户连接
public static void addSession(String userId, WebSocketSession session) {
sessions.put(userId, session);
}
// 移除用户连接
public static void removeSession(String userId) {
sessions.remove(userId);
}
// 发送消息
public static void sendMessage(String userId, String message) {
WebSocketSession session = sessions.get(userId);
if (session != null && session.isOpen()) {
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
WebSocket 处理器
在 MyWebSocketHandler 里,处理用户连接、消息接收和断开连接。
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Map;
public class MyWebSocketHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 从请求参数获取 userId,例如 ws://localhost:8080/ws?userId=123
Map<String, String> params = session.getAttributes();
String userId = (String) session.getAttributes().get("userId");
if (userId != null) {
WebSocketSessionManager.addSession(userId, session);
session.sendMessage(new TextMessage("连接成功,您的 ID: " + userId));
} else {
session.close();
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息: " + message.getPayload());
session.sendMessage(new TextMessage("服务器收到:" + message.getPayload()));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userId = (String) session.getAttributes().get("userId");
if (userId != null) {
WebSocketSessionManager.removeSession(userId);
}
System.out.println("用户 " + userId + " 断开连接");
}
}
-
WebSocket 配置
注册 WebSocket 端点,并支持 userId 作为参数。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/ws")
.setAllowedOrigins("*")
.addInterceptors(new WebSocketHandshakeInterceptor());
}
}
-
解析
userId
创建 WebSocketHandshakeInterceptor,用于解析 userId 并存入 session。
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(
org.springframework.http.server.ServerHttpRequest request,
org.springframework.http.server.ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String userId = servletRequest.getServletRequest().getParameter("userId");
if (userId != null) {
attributes.put("userId", userId);
return true;
}
}
return false;
}
@Override
public void afterHandshake(org.springframework.http.server.ServerHttpRequest request,
org.springframework.http.server.ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
}
-
业务中如何推送消息
在 Spring 业务代码(如订单更新服务)中,调用 WebSocketSessionManager.sendMessage() 发送通知。
示例:订单状态更新时推送 WebSocket 消息
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void updateOrderStatus(String orderId, String userId, String status) {
// 更新数据库订单状态(省略数据库操作)
System.out.println("订单 " + orderId + " 状态变更为:" + status);
// WebSocket 推送通知
WebSocketSessionManager.sendMessage(userId, "您的订单 " + orderId + " 状态更新为:" + status);
}
}
- 客户端测试
可以使用 JavaScript 进行 WebSocket 测试:
const userId = "123"; // 假设用户 ID 为 123
const socket = new WebSocket(
ws://localhost:8080/ws?userId=${userId}
);
socket.onopen = function () {
console.log("WebSocket 连接成功");
socket.send("Hello Server!");
};
socket.onmessage = function (event) {
console.log("收到服务器消息: " + event.data);
};
socket.onclose = function () {
console.log("WebSocket 连接已关闭");
};
-
运行和测试
- 启动 Spring Boot 应用
- 在浏览器打开控制台,运行 JavaScript 代码
- 在后端调用
OrderService.updateOrderStatus("1001", "123", "已发货") - 前端 WebSocket 收到推送消息
-
代码总结
WebSocketSessionManager:管理 WebSocket 连接并推送消息MyWebSocketHandler:WebSocket 事件处理(连接、消息、断开)WebSocketConfig:注册 WebSocket 端点WebSocketHandshakeInterceptor:从请求中解析userIdOrderService:业务层调用WebSocketSessionManager.sendMessage()发送通知
-
适用场景
✅ 消息推送(订单状态、系统通知) ✅ 在线聊天(可以扩展 sessions 维护群聊) ✅ 实时数据更新(股票、天气、物流跟踪)
如果需要 群聊,可以修改 WebSocketSessionManager 存储多个用户,并在 sendMessage() 方法中向所有用户发送消息。