WebSocket 在 Spring Boot 中的实战解析:实时通信的技术利器
一、引言:为什么我们需要 WebSocket?
在传统的 Web 应用中,客户端(浏览器)与服务器之间的通信是 请求-响应 模式:客户端发起请求,服务器处理后返回结果。这种模式适用于大多数场景,但在需要 实时双向通信 的场景下(如聊天室、股票行情、在线协作、游戏等),频繁轮询(Polling)或长轮询(Long Polling)会带来高延迟、高开销的问题。
WebSocket 协议应运而生------它提供了一种全双工、低延迟、持久化的通信通道,允许服务器主动向客户端推送数据,彻底改变了 Web 实时交互的格局。
在 Spring Boot 生态中,通过 spring-boot-starter-websocket 模块,我们可以轻松集成 WebSocket,并结合 STOMP(Simple Text-Oriented Messaging Protocol)实现更高级的消息路由与订阅机制。
1.1 从"轮询"到"长连接"的进化史
在WebSocket出现之前,实现实时通信主要靠这些"土办法":
| 技术方案 | 工作原理 | 缺点 |
|---|---|---|
| 短轮询 | 客户端每隔几秒问一次:"有新消息吗?" | 浪费带宽,实时性差 |
| 长轮询 | 客户端问"有新消息吗?",服务器hold住,有消息才回复 | 连接占用时间长,服务器压力大 |
| SSE | 服务器单向推送,客户端只能接收 | 单向通信,功能有限 |
WebSocket的登场改变了游戏规则:
- 一次握手,持久连接:建立连接后,双向通道一直打开
- 服务端主动推送:服务器想什么时候发就什么时候发
- 极低的通信开销:没有HTTP头部的重复传输
1.2 WebSocket vs HTTP:本质区别
HTTP交互流程(像发短信):

每次请求都要重复:建立TCP连接 → TLS握手 → 发送HTTP头部 → 传输数据 → 断开连接
WebSocket交互流程(像打电话):

二者的关键区别:

二、Spring Boot 中的 WebSocket 技术栈
Spring 对 WebSocket 的支持分为两个层次:
| 层级 | 技术 | 说明 |
|---|---|---|
| 底层 | javax.websocket 或 Spring 原生 WebSocket |
直接处理原始 WebSocket 消息 |
| 高层 | STOMP over WebSocket | 基于消息代理的发布/订阅模型,更易开发 |
✅ 推荐使用 STOMP:它提供了类似 JMS 的语义(目的地、订阅、广播),适合复杂业务场景。
2.1 核心注解与类
| 组件 | 作用 |
|---|---|
@EnableWebSocketMessageBroker |
启用 WebSocket 消息代理 |
WebSocketMessageBrokerConfigurer |
配置 STOMP 端点与消息代理 |
@MessageMapping |
映射客户端发送到特定路径的消息 |
SimpMessagingTemplate |
服务端主动向客户端推送消息 |
2.2 三步快速集成
第一步:添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
第二步:配置WebSocket
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册处理器,指定连接路径
registry.addHandler(myWebSocketHandler(), "/ws")
.setAllowedOrigins("*") // 生产环境要限制具体域名
.withSockJS(); // 为不支持WebSocket的浏览器提供降级方案
}
@Bean
public WebSocketHandler myWebSocketHandler() {
return new MyWebSocketHandler();
}
}
第三步:实现核心处理器
java
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
// 保存所有活跃连接
private static final Map<String, WebSocketSession> sessions =
new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// 连接建立时调用
String userId = extractUserId(session);
sessions.put(userId, session);
log.info("用户 {} 连接成功,当前在线: {} 人", userId, sessions.size());
// 发送欢迎消息
session.sendMessage(new TextMessage("连接成功!"));
}
@Override
protected void handleTextMessage(WebSocketSession session,
TextMessage message) {
// 处理客户端发送的消息
String payload = message.getPayload();
log.info("收到消息: {}", payload);
// 处理业务逻辑,比如广播或定向回复
handleMessage(session, payload);
}
@Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus status) {
// 连接关闭时调用
String userId = extractUserId(session);
sessions.remove(userId);
log.info("用户 {} 断开连接,原因: {}", userId, status);
}
}
2.3 进阶:使用STOMP协议
2.3.1 什么是STOMP?
STOMP(Simple Text Oriented Messaging Protocol)是一个简单的文本协议,它为WebSocket提供了更高级的消息模式。如果说原始的WebSocket是"裸奔",那么STOMP就是给它穿上了"协议的外衣"。
STOMP的核心概念:
- Destination(目的地):消息发送的目标地址
- SUBSCRIBE(订阅):客户端订阅某个目的地
- SEND(发送):客户端向目的地发送消息
- MESSAGE(消息):服务器向客户端推送消息
2.3.2 STOMP协议结构
一个简单的STOMP帧示例:
bash
SEND
destination:/app/chat
content-type:application/json
content-length:23
{"text":"Hello World!"}
响应帧:
perl
MESSAGE
destination:/topic/chat
content-type:application/json
content-length:45
subscription:sub-0
message-id:msg-123
{"user":"Tom","text":"Hello World!"}
2.3.3 SpringBoot中的STOMP配置
java
@Configuration
@EnableWebSocketMessageBroker // 启用STOMP消息代理
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册STOMP端点
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("*")
.withSockJS(); // SockJS支持
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 配置消息代理
registry.enableSimpleBroker("/topic", "/queue"); // 客户端订阅的前缀
registry.setApplicationDestinationPrefixes("/app"); // 客户端发送消息的前缀
registry.setUserDestinationPrefix("/user"); // 用户私信前缀
}
}
2.3.4 STOMP控制器示例
java
@Controller
public class ChatController {
// 处理发送到/app/chat的消息
@MessageMapping("/chat")
@SendTo("/topic/chat") // 广播给所有订阅/topic/chat的客户端
public ChatMessage handleChatMessage(ChatMessage message) {
message.setTimestamp(LocalDateTime.now());
log.info("收到聊天消息: {}", message);
return message;
}
// 处理私信
@MessageMapping("/private")
public void sendPrivateMessage(@Payload PrivateMessage message,
SimpMessageHeaderAccessor headerAccessor) {
// 获取发送者
String sender = headerAccessor.getUser().getName();
// 使用convertAndSendToUser发送给特定用户
simpMessagingTemplate.convertAndSendToUser(
message.getRecipient(), // 接收者用户名
"/queue/private", // 用户私信队列
new PrivateMessage(sender, message.getRecipient(), message.getContent())
);
}
// 处理订阅通知
@EventListener
public void handleSessionSubscribe(SessionSubscribeEvent event) {
StompHeaderAccessor headers = StompHeaderAccessor.wrap(event.getMessage());
String destination = headers.getDestination();
String sessionId = headers.getSessionId();
log.info("Session {} 订阅了 {}", sessionId, destination);
}
}
2.4.5 前端STOMP客户端示例
javascript
// 连接STOMP服务器
const socket = new SockJS('/ws-stomp');
const stompClient = Stomp.over(socket);
// 连接成功回调
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
// 订阅公共聊天频道
stompClient.subscribe('/topic/chat', function(message) {
const chatMsg = JSON.parse(message.body);
showMessage(chatMsg);
});
// 订阅个人私信队列
stompClient.subscribe('/user/queue/private', function(message) {
const privateMsg = JSON.parse(message.body);
showPrivateMessage(privateMsg);
});
});
// 发送聊天消息
function sendMessage() {
const message = {
content: document.getElementById('message').value,
sender: currentUser
};
stompClient.send("/app/chat", {}, JSON.stringify(message));
}
// 发送私信
function sendPrivateMessage(toUser, content) {
const message = {
recipient: toUser,
content: content
};
stompClient.send("/app/private", {}, JSON.stringify(message));
}
三、WebSocket的核心技术要点
3.1 连接生命周期管理
连接生命周期包括四个关键阶段,每个阶段需要处理不同的业务逻辑:
建立连接时(afterConnectionEstablished)需要处理:
- 验证用户身份 - 检查token或session,确保连接合法性
- 初始化会话状态 - 创建用户会话上下文,保存必要信息
- 通知相关服务用户上线 - 更新用户在线状态,通知好友
- 发送未读消息 - 推送离线期间积累的消息
java
@Component
public class WebSocketLifecycleManager {
public void onOpen(String sessionId, String userId) {
// 1. 验证用户身份
if (!userService.validateToken(userId, getToken(sessionId))) {
closeConnection(sessionId, CloseStatus.NOT_ACCEPTABLE);
return;
}
// 2. 初始化会话状态
SessionContext context = new SessionContext(userId, sessionId);
sessionStore.save(sessionId, context);
// 3. 通知相关服务用户上线
presenceService.userOnline(userId);
notifyFriends(userId, true); // 通知好友用户上线
// 4. 发送未读消息
List<Message> unreadMessages = messageService.getUnreadMessages(userId);
unreadMessages.forEach(msg -> sendMessage(sessionId, msg));
}
}
消息处理时(handleTextMessage)需要处理:
- 消息格式验证 - 检查JSON格式、必要字段
- 业务逻辑处理 - 根据消息类型执行不同业务
- 消息持久化 - 保存到数据库,确保不丢失
- 响应或转发 - 回复发送者或转发给其他用户
java
public void onMessage(String sessionId, String rawMessage) {
// 1. 消息格式验证
Message message;
try {
message = jsonMapper.readValue(rawMessage, Message.class);
validateMessage(message);
} catch (Exception e) {
sendError(sessionId, "消息格式错误");
return;
}
// 2. 业务逻辑处理
switch (message.getType()) {
case "CHAT":
handleChatMessage(sessionId, message);
break;
case "COMMAND":
handleCommand(sessionId, message);
break;
// ... 其他消息类型
}
// 3. 消息持久化
messageService.saveMessage(message);
// 4. 响应或转发
if (message.needResponse()) {
sendResponse(sessionId, createResponse(message));
}
if (message.needForward()) {
forwardMessage(message.getTarget(), message);
}
}
连接关闭时(afterConnectionClosed)需要处理:
- 清理会话资源 - 释放内存,关闭相关资源
- 更新用户状态为离线 - 标记用户下线时间
- 记录断开原因 - 用于分析连接稳定性
- 通知相关服务 - 通知好友用户下线
java
public void onClose(String sessionId, CloseStatus status) {
// 1. 清理会话资源
SessionContext context = sessionStore.remove(sessionId);
if (context != null) {
context.cleanup();
}
// 2. 更新用户状态为离线
String userId = getUserIdFromSession(sessionId);
presenceService.userOffline(userId);
// 3. 记录断开原因
connectionLogService.logDisconnect(sessionId, userId, status.getCode(), status.getReason());
// 4. 通知相关服务
notifyFriends(userId, false); // 通知好友用户下线
cleanupUserSubscriptions(userId); // 清理用户的所有订阅
}
错误时(handleTransportError)需要处理:
- 记录错误日志 - 详细记录异常信息
- 尝试恢复连接 - 对于可恢复错误尝试重连
- 通知监控系统 - 触发告警,人工干预
- 优雅降级 - 切换到备用通信方式
java
public void onError(String sessionId, Throwable error) {
// 1. 记录错误日志
log.error("WebSocket连接错误 sessionId: {}", sessionId, error);
// 2. 尝试恢复连接(如果是网络波动等临时错误)
if (isRecoverableError(error)) {
scheduleReconnection(sessionId);
} else {
// 3. 通知监控系统
alertService.sendAlert("WebSocket连接异常",
"Session: " + sessionId + ", Error: " + error.getMessage());
// 4. 优雅降级
fallbackToHttp(sessionId, getUserIdFromSession(sessionId));
closeConnection(sessionId, CloseStatus.SERVER_ERROR);
}
}
3.2 心跳机制与健康检查
java
@Configuration
public class HeartbeatConfig {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container =
new ServletServerContainerFactoryBean();
// 重要配置
container.setMaxSessionIdleTimeout(300000L); // 5分钟无活动断开
container.setMaxTextMessageBufferSize(8192); // 最大消息大小
container.setMaxBinaryMessageBufferSize(8192);
container.setAsyncSendTimeout(5000L); // 异步发送超时
return container;
}
}
// 心跳检测实现
@Component
public class HeartbeatService {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
@PostConstruct
public void startHeartbeat() {
scheduler.scheduleAtFixedRate(() -> {
checkConnections();
sendPing();
}, 30, 30, TimeUnit.SECONDS); // 每30秒检测一次
}
private void checkConnections() {
// 检查所有连接的健康状态
// 移除僵尸连接
// 记录连接统计信息
}
private void sendPing() {
// 向所有活跃连接发送ping消息
// 处理未响应pong的连接
}
}
3.3 消息可靠性与重连机制
java
public class ReliableMessageService {
// 消息确认机制
public void sendWithAck(String sessionId, String message) {
String msgId = generateMsgId();
// 发送消息
webSocketHandler.send(sessionId, wrapMessage(msgId, message));
// 启动确认计时器
scheduler.schedule(() -> {
if (!isAcked(msgId)) {
log.warn("消息 {} 未确认,尝试重发", msgId);
retrySend(sessionId, msgId, message);
}
}, 5, TimeUnit.SECONDS);
}
// 客户端重连处理
public void handleReconnect(String oldSessionId, String newSessionId) {
// 1. 转移会话状态
// 2. 重发未确认消息
// 3. 恢复订阅关系
// 4. 更新会话映射
}
// 消息去重
public boolean isDuplicate(String msgId) {
// 基于Redis或本地缓存实现
// 防止重复处理消息
return false;
}
}
四、实战:写一个监控SpringBoot应用的实时监控告警系统
Spring Insight 是我的一个开源项目,目前正在紧张的开发中。项目地址:github.com/iweidujiang...,欢迎关注,顺便求个 star,哈哈。

在监控诊断类工具中,WebSocket 可以:
- 实时告警:第一时间发现问题
- 动态拓扑:实时展示微服务依赖变化
- 性能监控:实时推送指标数据
- 在线诊断:实时查看日志和跟踪信息
例,实时统计当前监控信息:
java
/**
* 广播实时统计信息(每5秒一次)
*/
@Scheduled(fixedDelay = 5000)
public void broadcastStats() {
if (connectionCount.get() == 0) return;
try {
// 获取最新数据
var collectorStats = dataCollectorService.getCollectorStats();
var serviceStats = dataCollectorService.getServiceStats();
var errorAnalysis = dataCollectorService.getErrorAnalysis(1); // 最近1小时
// 构建消息
Map<String, Object> data = new HashMap<>();
data.put("collectorStats", collectorStats);
data.put("serviceStats", serviceStats.subList(0, Math.min(5, serviceStats.size())));
data.put("errorAnalysis", errorAnalysis.subList(0, Math.min(5, errorAnalysis.size())));
data.put("timestamp", Instant.now().toString());
data.put("cacheSize", dataCollectorService.getCacheSize());
WebSocketMessage message = new WebSocketMessage();
message.setType("STATS_UPDATE");
message.setData(data);
// 广播消息
messagingTemplate.convertAndSend("/topic/stats", message);
log.debug("广播实时统计信息");
} catch (Exception e) {
log.error("广播实时统计信息失败", e);
}
}
五、其他典型使用场景
| 场景 | 说明 | WebSocket 优势 |
|---|---|---|
| 在线客服/聊天系统 | 用户与客服实时对话 | 低延迟、支持多房间 |
| 股票/金融行情推送 | 实时价格更新 | 减少服务器压力,避免轮询 |
| 协同编辑 | 多人同时编辑文档 | 实时同步操作,冲突检测 |
| 游戏状态同步 | 多人在线小游戏 | 高频消息传递,毫秒级响应 |
| IoT 设备监控 | 传感器数据上报 | 长连接节省资源 |
✅ 用WebSocket:
- 实时双向通信需求(聊天、协作)
- 高频数据推送(监控、行情)
- 低延迟要求(游戏、实时控制)
- 服务端主动通知(告警、状态更新)
❌ 不用WebSocket:
- 简单的请求-响应模式(REST API足够)
- 客户端偶尔拉取数据(用HTTP轮询)
- 单向信息流(考虑SSE)
- 移动端弱网络环境(可能连接不稳定)
六、总结
WebSocket 是构建现代实时 Web 应用的基石。Spring Boot 通过简洁的配置和强大的 STOMP 支持,让开发者能够快速实现高性能、可扩展的双向通信系统。
记住 :不是所有场景都需要 WebSocket。对于低频更新(如每分钟一次),传统 REST + 定时轮询可能更简单。但在高频、低延迟、事件驱动的场景下,WebSocket 几乎是唯一选择。
我的技术思考和实践,统一沉淀在这些地方:
🌍 微信公众号:「苏渡苇」
推送最稳定,适合深度阅读
⭐️ 掘金:「苏渡苇」
💡 知乎:「苏渡苇」
📘 CSDN:「苏渡苇」
🐙 GitHub:github.com/iweidujiang
所有代码的源头,包括 Spring Insight 开源项目,感谢 Star ⭐
非常感谢你关注我。