一、什么是 spring-boot-starter-websocket
spring-boot-starter-websocket 是 Spring Boot 提供的一个自动配置启动器(Starter),用于快速集成 WebSocket 协议支持。它封装了 Spring Framework 的 WebSocket 功能,包括 STOMP(Simple Text Oriented Messaging Protocol)消息协议支持,使得开发者可以轻松地在 Spring Boot 应用中实现双向实时通信。
核心依赖包含:
spring-websocket:WebSocket 协议核心支持spring-messaging:消息抽象层,支持 STOMPsockjs:兼容性库,用于在不支持 WebSocket 的浏览器中模拟 WebSocket(如降级为轮询)stomp-websocket:STOMP 协议客户端/服务端实现
✅ 一句话总结:
spring-boot-starter-websocket让你只需几行配置,就能在 Spring Boot 应用中实现类似微信聊天、实时股票行情、在线协作编辑器、游戏实时状态同步等场景的双向通信功能。
二、WebSocket 与 STOMP 的区别
| 特性 | WebSocket | STOMP over WebSocket |
|---|---|---|
| 协议层级 | 原始二进制/文本协议 | 应用层消息协议(基于 WebSocket) |
| 消息格式 | 自定义 | 标准化格式(SEND, SUBSCRIBE, MESSAGE 等) |
| 开发复杂度 | 高(需自己设计协议) | 低(有标准命令和消息头) |
| 适用场景 | 高性能实时流(如游戏) | 企业级应用(聊天、通知、仪表盘) |
| Spring 支持 | 基础支持 | 完整封装(推荐) |
💡 推荐使用 STOMP:在大多数业务系统中,STOMP 更易用、可维护、可扩展,Spring Boot 对其支持最完善。
三、主要功能与应用场景
| 功能 | 描述 | 实际应用场景 |
|---|---|---|
| 实时消息推送 | 服务端主动向客户端推送消息 | 股票行情、系统通知、订单状态更新 |
| 多用户聊天 | 多个客户端之间互相发送消息 | 在线客服、团队协作聊天 |
| 用户在线状态 | 实时感知用户上线/下线 | 社交应用、IM系统 |
| 实时数据监控 | 实时展示设备/服务器指标 | 监控大屏、IoT 数据看板 |
| 协同编辑 | 多人同时编辑同一文档 | Google Docs 类型应用 |
| 游戏实时同步 | 玩家动作同步、状态更新 | 浏览器多人小游戏 |
四、完整开发示例:实时聊天室系统(含详细中文注释)
✅ 项目结构
src/
├── main/
│ ├── java/
│ │ └── com/example/websocketdemo/
│ │ ├── WebSocketDemoApplication.java // 启动类
│ │ ├── config/WebSocketConfig.java // WebSocket 配置
│ │ ├── controller/ChatController.java // 消息控制器
│ │ └── service/ChatService.java // 业务服务
│ └── resources/
│ └── static/
│ └── chat.html // 前端聊天页面
1. Maven 依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>websocket-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- 引入 WebSocket 启动器,自动配置所有必要组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Web 依赖,提供 REST 接口和静态资源服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 可选:热部署开发工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. WebSocket 配置类(WebSocketConfig.java)
package com.example.websocketdemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket 消息代理配置类
*
* 作用:配置 STOMP 消息代理、端点(Endpoint)、消息路由规则
*
* @EnableWebSocketMessageBroker:启用 WebSocket 消息代理功能
* - 自动配置 SockJS、STOMP、消息代理(内存或外部如 Redis)
* - 启用 @MessageMapping 和 @SubscribeMapping 注解
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 注册 STOMP 端点(客户端连接入口)
*
* 为什么需要端点?
* - 客户端通过此 URL 建立 WebSocket 连接(如:ws://localhost:8080/chat)
* - SockJS 会降级为 HTTP 长轮询,兼容老浏览器
*
* withSockJS():启用 SockJS 兼容模式(推荐!)
* - 当浏览器不支持原生 WebSocket 时,自动降级为 AJAX 轮询
* - 保证在 IE9+、移动端等环境也能正常工作
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册一个名为 "/chat" 的端点
registry.addEndpoint("/chat")
.setAllowedOriginPatterns("*") // 允许所有来源(开发阶段使用,生产环境请限制)
.withSockJS(); // 启用 SockJS 兼容模式
}
/**
* 配置消息代理(Message Broker)
*
* 两种代理类型:
* 1. 内存代理(simple):适用于单机应用,消息存储在 JVM 内存中
* 2. 外部代理(如 Redis):适用于集群部署,消息跨节点同步
*
* setApplicationDestinationPrefixes:指定客户端发送消息的前缀
* - 客户端发送消息时,必须以 "/app" 开头,如:/app/chat
* - Spring 会将这些消息路由到 @MessageMapping 注解的方法
*
* setUserDestinationPrefix:指定用户私有消息前缀
* - 用于向特定用户发送消息,如:/user/{username}/queue/messages
* - 需要结合 @SendToUser 使用
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 配置客户端可订阅的主题前缀(广播/群组消息)
// 客户端使用 subscribe("/topic/public") 订阅公共消息
registry.enableSimpleBroker("/topic", "/queue");
// 配置客户端发送消息的目标前缀
// 客户端发送消息必须以 /app 开头,如:/app/chat
// Spring 会将这些消息映射到 @MessageMapping("/chat") 的方法
registry.setApplicationDestinationPrefixes("/app");
// 配置用户私有消息前缀,用于一对一消息推送
// 如:/user/张三/queue/messages
registry.setUserDestinationPrefix("/user");
}
}
3. 消息控制器(ChatController.java)
package com.example.websocketdemo.controller;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* STOMP 消息控制器
*
* 作用:接收客户端通过 WebSocket 发送的消息,并广播或定向发送给其他客户端
*
* @Controller:标记为 Spring 控制器,但不返回视图,只处理消息
* @MessageMapping:映射客户端发送的消息路径(与 setApplicationDestinationPrefixes 配合)
* @SendTo:将消息广播到指定主题(所有订阅者都能收到)
* @SubscribeMapping:当客户端订阅某个主题时自动触发(用于初始化数据)
*/
@Controller
public class ChatController {
/**
* 接收客户端发送的聊天消息
*
* 客户端发送路径:/app/chat
* 此方法会自动接收所有发送到 /app/chat 的消息
*
* @Payload:自动将消息体(JSON)绑定为 ChatMessage 对象
* @SendTo("/topic/public"):将消息广播到 /topic/public 主题
* - 所有订阅了 /topic/public 的客户端都会收到这条消息
*
* 注意:消息体必须是 JSON 格式,Spring 自动反序列化为 Java 对象
*/
@MessageMapping("/chat")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage message) {
// 为消息添加时间戳,便于前端展示
message.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
return message; // 自动序列化为 JSON 并广播到 /topic/public
}
/**
* 处理用户登录事件(可选)
*
* 当客户端首次连接并订阅 /user/{username}/queue/messages 时触发
* 可用于发送欢迎消息、在线状态更新等
*/
@MessageMapping("/user.login")
@SendToUser("/queue/notifications")
public ChatMessage handleUserLogin(@Payload ChatMessage message, SimpMessageHeaderAccessor headerAccessor) {
String username = message.getFrom(); // 从消息中获取用户名
ChatMessage response = new ChatMessage();
response.setFrom("系统");
response.setContent(username + " 已上线!");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
return response;
}
/**
* 当客户端订阅 /app/hello 时,自动返回欢迎信息
*
* 与 @MessageMapping 不同:
* - @MessageMapping:客户端主动发送消息触发
* - @SubscribeMapping:客户端订阅主题时自动触发(无需客户端主动发消息)
*
* 适用于:首次连接时返回欢迎语、初始化数据、在线人数统计等
*/
@SubscribeMapping("/app/hello")
public ChatMessage handleHello() {
ChatMessage message = new ChatMessage();
message.setFrom("系统");
message.setContent("欢迎使用 WebSocket 聊天室!");
message.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
return message;
}
}
4. 消息实体类(ChatMessage.java)
package com.example.websocketdemo.controller;
import java.time.LocalDateTime;
/**
* 聊天消息实体类
*
* 作用:定义客户端与服务端之间传输的消息结构
*
* 注意:
* - 必须是 POJO(普通 Java 对象)
* - 字段名必须与前端 JSON 字段名一致(默认是 camelCase)
* - Spring 会自动将 JSON 反序列化为此对象
* - 也可以使用 @JsonProperty 注解指定字段映射
*/
public class ChatMessage {
private String from; // 发送者用户名
private String content; // 消息内容
private String timestamp; // 发送时间(字符串格式)
// 无参构造函数(Jackson 反序列化必需)
public ChatMessage() {}
// 全参构造函数(方便测试)
public ChatMessage(String from, String content, String timestamp) {
this.from = from;
this.content = content;
this.timestamp = timestamp;
}
// Getter 和 Setter 方法(必须提供,用于序列化/反序列化)
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
5. 前端聊天页面(resources/static/chat.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 聊天室</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 20px; }
#chat-box { width: 600px; height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; }
#message-input { width: 400px; padding: 8px; }
#send-btn { padding: 8px 16px; }
.system { color: #888; font-style: italic; }
.me { background-color: #e3f2fd; padding: 5px 10px; border-radius: 10px; margin: 5px 0; }
.other { background-color: #f0f0f0; padding: 5px 10px; border-radius: 10px; margin: 5px 0; }
</style>
</head>
<body>
<h2>🚀 WebSocket 实时聊天室</h2>
<div id="chat-box"></div>
<input type="text" id="message-input" placeholder="输入消息..." />
<button id="send-btn">发送</button>
<button id="connect-btn">连接</button>
<button id="disconnect-btn">断开</button>
<script>
// ---------------------- 初始化变量 ----------------------
let stompClient = null; // STOMP 客户端实例
let username = null; // 当前用户昵称
// ---------------------- 连接 WebSocket ----------------------
function connect() {
// 创建 SockJS 连接(兼容性好,自动降级)
// 服务端注册的端点是 /chat
const socket = new SockJS('http://localhost:8080/chat');
// 创建 STOMP 客户端
stompClient = Stomp.over(socket);
// 连接到服务器
stompClient.connect({}, function (frame) {
console.log('✅ 已连接到 WebSocket 服务器:', frame);
// 1. 订阅公共聊天主题(接收所有人的消息)
// - 服务端广播到 /topic/public,客户端订阅此主题接收
stompClient.subscribe('/topic/public', function (message) {
displayMessage(JSON.parse(message.body));
});
// 2. 订阅用户私有消息(用于接收系统通知)
// - 服务端使用 @SendToUser 发送,路径为 /user/{username}/queue/messages
stompClient.subscribe('/user/queue/notifications', function (message) {
displayMessage(JSON.parse(message.body), 'system');
});
// 3. 订阅欢迎消息(测试 @SubscribeMapping)
stompClient.subscribe('/app/hello', function (message) {
displayMessage(JSON.parse(message.body), 'system');
});
// 4. 设置当前用户名(模拟登录)
username = prompt("请输入您的昵称:") || "游客" + Math.floor(Math.random() * 100);
document.getElementById('message-input').placeholder = '你好,' + username + ',输入消息...';
// 5. 向服务端发送登录消息(可选)
stompClient.send("/app/user.login", {}, JSON.stringify({from: username}));
}, function (error) {
console.error('❌ 连接失败:', error);
alert('连接失败,请检查服务端是否启动!');
});
}
// ---------------------- 发送消息 ----------------------
function sendMessage() {
const input = document.getElementById('message-input');
const message = input.value.trim();
if (message && stompClient && stompClient.connected) {
// 发送消息到服务端 /app/chat 路径
// 服务端 @MessageMapping("/chat") 会接收此消息
stompClient.send("/app/chat", {}, JSON.stringify({
from: username, // 发送者
content: message, // 消息内容
timestamp: "" // 时间由服务端生成
}));
// 清空输入框
input.value = '';
} else {
alert('请先连接服务器!');
}
}
// ---------------------- 显示消息到页面 ----------------------
function displayMessage(message, type = 'other') {
const chatBox = document.getElementById('chat-box');
const msgDiv = document.createElement('div');
// 根据类型设置样式
if (type === 'system') {
msgDiv.className = 'system';
msgDiv.textContent = `[${message.timestamp}] ${message.from}: ${message.content}`;
} else if (message.from === username) {
msgDiv.className = 'me';
msgDiv.textContent = `${message.from} (${message.timestamp}): ${message.content}`;
} else {
msgDiv.className = 'other';
msgDiv.textContent = `${message.from} (${message.timestamp}): ${message.content}`;
}
chatBox.appendChild(msgDiv);
chatBox.scrollTop = chatBox.scrollHeight; // 自动滚动到底部
}
// ---------------------- 绑定事件 ----------------------
document.getElementById('send-btn').addEventListener('click', sendMessage);
document.getElementById('message-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
sendMessage();
}
});
document.getElementById('connect-btn').addEventListener('click', connect);
document.getElementById('disconnect-btn').addEventListener('click', function () {
if (stompClient && stompClient.connected) {
stompClient.disconnect();
console.log('👋 已断开连接');
alert('已断开连接');
}
});
// 页面加载完成后自动尝试连接
window.onload = function () {
connect();
};
</script>
</body>
</html>
6. 启动类(WebSocketDemoApplication.java)
package com.example.websocketdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot 启动类
*
* 作用:启动整个 Spring Boot 应用
*
* @SpringBootApplication:组合注解,包含:
* - @Configuration:标记为配置类
* - @EnableAutoConfiguration:启用自动配置
* - @ComponentScan:扫描包下所有组件(Controller、Service、Config 等)
*/
@SpringBootApplication
public class WebSocketDemoApplication {
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(WebSocketDemoApplication.class, args);
System.out.println("🎉 WebSocket 聊天室服务已启动,请访问:http://localhost:8080/chat.html");
}
}
五、运行与测试
✅ 启动步骤:
-
启动服务端:
mvn spring-boot:run控制台输出:
🎉 WebSocket 聊天室服务已启动,请访问:http://localhost:8080/chat.html -
打开浏览器:
- 访问
http://localhost:8080/chat.html - 输入昵称(如:张三)
- 打开另一个浏览器标签页(或使用隐身模式),输入另一个昵称(如:李四)
- 访问
-
测试效果:
- 张三发送消息 → 李四立即收到
- 李四回复 → 张三立即收到
- 系统自动发送欢迎语
- 断开重连 → 重新连接后仍可接收消息
六、进阶优化建议
| 优化方向 | 说明 |
|---|---|
| 用户认证 | 结合 Spring Security,在连接时验证 Token,绑定用户身份 |
| 集群部署 | 使用 Redis 作为 STOMP 消息代理(替换 enableSimpleBroker → enableStompBrokerRelay) |
| 心跳检测 | 配置 heartbeat 避免连接被防火墙断开 |
| 消息持久化 | 将聊天记录存入数据库,支持历史消息查询 |
| 消息限流 | 防止刷屏,使用 @RateLimit 或 SimpMessagingTemplate 限流 |
| 前端框架集成 | 可与 Vue/React/Angular 集成,使用 vue-stomp、ngx-stomp 等库 |
七、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
Connection refused | 服务未启动或端口被占用 | 检查控制台日志,确认端口 8080 是否被占用 |
Cannot connect to WebSocket endpoint | 前端地址错误 | 确保 new SockJS('http://localhost:8080/chat') 与服务端 addEndpoint("/chat") 一致 |
404 Not Found | 静态资源未加载 | 确保 chat.html 放在 src/main/resources/static/ 目录下 |
| 消息收不到 | 订阅路径错误 | 检查 subscribe('/topic/public') 是否与 @SendTo("/topic/public") 完全一致 |
| 跨域问题 | 浏览器报错 | 在 registerStompEndpoints 中添加 .setAllowedOriginPatterns("*") |
八、总结
| 维度 | 说明 |
|---|---|
| 核心价值 | 实现低延迟、双向、实时通信,替代轮询和长轮询 |
| 技术优势 | 基于 Spring 生态,开发效率高,兼容性好,文档丰富 |
| 适用场景 | 聊天、通知、监控、协同编辑、实时数据推送 |
| 推荐架构 | Spring Boot + STOMP + SockJS + HTML5 |
| 生产建议 | 集群部署时使用 Redis 作为消息代理,配合 Spring Security 做权限控制 |
✅ 最佳实践:优先使用
@MessageMapping+@SendTo+SockJS模式,避免直接使用原始 WebSocket,降低开发复杂度。
附录:完整项目 GitHub 模板(可直接克隆)
git clone https://github.com/example/spring-boot-websocket-chat-demo.git
cd spring-boot-websocket-chat-demo
mvn spring-boot:run
📌 本示例已通过 Spring Boot 3.2 + Java 17 测试,可直接用于生产项目参考。
💡 结语:spring-boot-starter-websocket 是实现现代 Web 实时交互的“瑞士军刀”。掌握它,你就能轻松构建微信、钉钉、飞书、股票行情、在线游戏等主流应用的实时通信功能!
879

被折叠的 条评论
为什么被折叠?



