在现代Web应用中,实时通信需求日益增长,如在线聊天、实时数据监控、消息推送等场景。传统的HTTP协议基于“请求-响应”模式,无法满足服务器主动向客户端推送数据的需求。而WebSocket协议作为HTML5的重要特性,实现了浏览器与服务器之间的全双工通信,为实时通信提供了高效解决方案。本文将详细介绍如何在Spring Boot项目中集成并实现WebSocket功能。
一、WebSocket核心概念
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器主动向客户端发送数据,而无需客户端先发起请求。其核心特点包括:
- 全双工通信:客户端与服务器可同时发送数据,通信效率高;
- 持久连接:连接建立后持续存在,避免了HTTP多次握手的开销;
- 轻量协议:数据帧格式简单,头部开销小,传输效率优于HTTP;
- 基于TCP:依托TCP协议保证数据传输的可靠性。
WebSocket的连接建立过程:客户端通过HTTP请求发起握手,请求头中携带Upgrade: websocket和Connection: Upgrade等字段,表明希望将HTTP连接升级为WebSocket连接;服务器同意升级后,双方后续通信即使用WebSocket协议。
二、Spring Boot对WebSocket的支持
Spring Boot通过spring-boot-starter-websocket starter模块提供了对WebSocket的快速集成支持,底层基于Spring WebSocket模块实现。Spring WebSocket不仅实现了WebSocket协议规范,还提供了以下核心特性:
- 基于注解的编程模型,简化WebSocket处理器开发;
- 内置消息代理(如SockJS),解决浏览器不支持WebSocket的兼容问题;
- 支持STOMP协议(Simple Text Oriented Messaging Protocol),提供更强大的消息路由和订阅/发布功能;
- 与Spring Security集成,可实现WebSocket连接的权限控制。
三、Spring Boot实现WebSocket的两种方式
Spring Boot中实现WebSocket主要有两种场景:一是基于原生WebSocket API实现简单的点对点通信;二是基于STOMP协议实现复杂的订阅/发布模式(如群聊、广播通知)。下面分别详细介绍实现步骤。
方式一:基于原生WebSocket API实现
适用于简单的实时通信场景,如点对点聊天。核心是实现WebSocketHandler接口(或继承抽象类TextWebSocketHandler)处理消息收发,通过WebSocketConfigurer配置WebSocket连接路径。
步骤1:引入依赖
在Spring Boot项目的pom.xml(Maven)或build.gradle(Gradle)中引入WebSocket starter依赖:
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
步骤2:实现WebSocket处理器
创建自定义处理器类,继承TextWebSocketHandler,重写消息接收、连接建立/关闭等方法:
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* 自定义WebSocket处理器
*/
public class CustomWebSocketHandler extends TextWebSocketHandler {
/**
* 连接建立时触发
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("WebSocket连接建立:" + session.getId());
// 向客户端发送连接成功消息
session.sendMessage(new TextMessage("连接成功!"));
}
/**
* 接收客户端消息时触发
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
System.out.println("收到客户端消息:" + payload);
// 向客户端回复消息
session.sendMessage(new TextMessage("服务器已收到消息:" + payload));
}
/**
* 连接关闭时触发
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("WebSocket连接关闭:" + session.getId() + ",状态:" + status);
}
/**
* 连接异常时触发
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("WebSocket连接异常:" + session.getId() + ",异常信息:" + exception.getMessage());
}
}
步骤3:配置WebSocket
创建配置类,实现WebSocketConfigurer接口,通过@EnableWebSocket注解开启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;
/**
* WebSocket配置类
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册处理器,配置连接路径为/ws,允许跨域(开发环境可开启)
registry.addHandler(new CustomWebSocketHandler(), "/ws")
.setAllowedOrigins("*"); // 生产环境需指定具体域名,避免安全风险
}
}
步骤4:编写前端测试页面
创建HTML页面,使用原生WebSocket API与后端建立连接,实现消息收发:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket测试</title>
</head>
<body>
<h3>WebSocket测试页面</h3>
<input type="text" id="messageInput" placeholder="请输入消息">
<button onclick="sendMessage()">发送消息</button>
<div id="messageContainer" style="margin-top: 20px;"></div>
<script>
// 建立WebSocket连接(注意:HTTP对应ws,HTTPS对应wss)
const ws = new WebSocket("ws://localhost:8080/ws");
// 连接建立成功
ws.onopen = function() {
appendMessage("连接成功!");
};
// 接收服务器消息
ws.onmessage = function(event) {
appendMessage("服务器:" + event.data);
};
// 连接关闭
ws.onclose = function(event) {
appendMessage("连接关闭,状态码:" + event.code);
};
// 连接异常
ws.onerror = function(error) {
appendMessage("连接异常:" + error.message);
};
// 发送消息
function sendMessage() {
const input = document.getElementById("messageInput");
const message = input.value.trim();
if (message) {
ws.send(message);
appendMessage("客户端:" + message);
input.value = "";
}
}
// 追加消息到页面
function appendMessage(content) {
const container = document.getElementById("messageContainer");
const div = document.createElement("div");
div.textContent = new Date().toLocaleString() + ":" + content;
container.appendChild(div);
}
</script>
</body>
</html>
步骤5:测试验证
- 启动Spring Boot项目;
- 将测试页面放入项目的
resources/static目录下; - 打开浏览器访问
http://localhost:8080,输入消息并发送,可看到客户端与服务器的实时通信。
方式二:基于STOMP协议实现订阅/发布模式
原生WebSocket API适用于简单场景,但对于复杂的消息路由、订阅分组等需求,手动实现成本较高。STOMP协议(基于WebSocket的文本导向消息协议)提供了标准化的消息格式和订阅/发布机制,Spring Boot可通过集成STOMP快速实现群聊、广播通知等功能。
步骤1:引入依赖
与方式一相同,引入spring-boot-starter-websocket依赖即可(已包含STOMP相关组件)。
步骤2:配置STOMP
创建配置类,实现WebSocketMessageBrokerConfigurer接口,通过@EnableWebSocketMessageBroker注解开启WebSocket消息代理:
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;
/**
* STOMP协议配置类
*/
@Configuration
@EnableWebSocketMessageBroker // 开启WebSocket消息代理
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* 注册STOMP端点(客户端连接的入口)
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册端点/ws-stomp,允许跨域,启用SockJS fallback(兼容不支持WebSocket的浏览器)
registry.addEndpoint("/ws-stomp")
.setAllowedOrigins("*")
.withSockJS();
}
/**
* 配置消息代理
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 配置应用前缀:客户端发送到服务器的消息需以/app开头
config.setApplicationDestinationPrefixes("/app");
// 配置内置消息代理:服务器向客户端推送的消息以/topic(广播)或/queue(点对点)开头
config.enableSimpleBroker("/topic", "/queue");
// 配置用户前缀(点对点消息时使用)
config.setUserDestinationPrefix("/user");
}
}
步骤3:编写消息控制器
创建控制器,使用@MessageMapping注解接收客户端消息,通过@SendTo注解将消息广播到指定主题:
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import java.time.LocalDateTime;
/**
* STOMP消息控制器
*/
@Controller
public class StompMessageController {
/**
* 接收客户端发送到/app/chat的消息
* @param message 客户端消息(格式:用户名:消息内容)
* @return 广播的消息内容
*/
@MessageMapping("/chat") // 对应客户端发送的目的地:/app/chat
@SendTo("/topic/public") // 将返回结果广播到/topic/public主题
public ChatMessage handleChatMessage(String message) {
// 解析用户名和消息内容(实际项目中可通过Spring Security获取登录用户)
String[] parts = message.split(":", 2);
String username = parts.length > 0 ? parts[0].trim() : "匿名用户";
String content = parts.length > 1 ? parts[1].trim() : "";
// 构建消息对象
return new ChatMessage(
username,
content,
LocalDateTime.now().toString()
);
}
/**
* 消息实体类
*/
public static class ChatMessage {
private String username;
private String content;
private String time;
// 构造方法、getter、setter
public ChatMessage(String username, String content, String time) {
this.username = username;
this.content = content;
this.time = time;
}
// getter和setter省略
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getTime() { return time; }
public void setTime(String time) { this.time = time; }
}
}
步骤4:编写前端测试页面(基于STOMP.js)
STOMP客户端需要使用STOMP.js库与后端通信,需引入STOMP.js和SockJS.js(兼容不支持WebSocket的浏览器):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>STOMP群聊测试</title>
<!-- 引入SockJS和STOMP.js -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@6/dist/stomp.min.js"></script>
</head>
<body>
<h3>STOMP群聊测试</h3>
<input type="text" id="usernameInput" placeholder="请输入用户名">
<button onclick="connect()">连接</button>
<br><br>
<input type="text" id="messageInput" placeholder="请输入消息" disabled>
<button onclick="sendMessage()" disabled>发送消息</button>
<div id="messageContainer" style="margin-top: 20px; height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px;"></div>
<script>
let stompClient = null;
// 连接STOMP服务器
function connect() {
const username = document.getElementById("usernameInput").value.trim();
if (!username) {
alert("请输入用户名!");
return;
}
// 创建SockJS连接(兼容模式)
const socket = new SockJS("/ws-stomp");
// 创建STOMP客户端
stompClient = Stomp.over(socket);
// 连接成功回调
stompClient.connect({}, function(frame) {
console.log("STOMP连接成功:" + frame);
// 禁用用户名输入和连接按钮
document.getElementById("usernameInput").disabled = true;
document.getElementById("connect").disabled = true;
// 启用消息输入和发送按钮
document.getElementById("messageInput").disabled = false;
document.getElementById("sendMessage").disabled = false;
// 订阅/topic/public主题,接收广播消息
stompClient.subscribe("/topic/public", function(message) {
const chatMessage = JSON.parse(message.body);
appendMessage(`${chatMessage.time} - ${chatMessage.username}:${chatMessage.content}`);
});
});
// 连接失败回调
stompClient.onerror = function(error) {
console.error("STOMP连接失败:" + error);
appendMessage("连接失败,请重试!");
};
}
// 发送消息
function sendMessage() {
const input = document.getElementById("messageInput");
const content = input.value.trim();
const username = document.getElementById("usernameInput").value.trim();
if (content && stompClient) {
// 发送消息到/app/chat目的地(对应控制器的@MessageMapping("/chat"))
stompClient.send("/app/chat", {}, `${username}:${content}`);
input.value = "";
}
}
// 追加消息到页面
function appendMessage(content) {
const container = document.getElementById("messageContainer");
const div = document.createElement("div");
div.textContent = content;
container.appendChild(div);
// 滚动到底部
container.scrollTop = container.scrollHeight;
}
</script>
</body>
</html>
步骤5:测试验证
- 启动Spring Boot项目;
- 将测试页面放入
resources/static目录; - 打开多个浏览器窗口,分别输入不同用户名并连接;
- 在任意窗口发送消息,所有连接的窗口均可收到广播消息,实现群聊功能。
四、进阶优化:权限控制与连接管理
1. 权限控制(集成Spring Security)
实际项目中,需对WebSocket连接进行权限验证(如仅登录用户可连接)。可通过Spring Security拦截WebSocket连接,结合Principal获取当前登录用户信息:
// 在STOMP控制器中获取当前登录用户
@MessageMapping("/chat")
@SendTo("/topic/public")
public ChatMessage handleChatMessage(String message, Principal principal) {
String username = principal.getName(); // 获取登录用户名
// 后续逻辑...
}
2. 连接管理
可通过WebSocketSession(原生API)或SimpMessageHeaderAccessor(STOMP)管理连接状态,如统计在线人数、强制断开连接等。
3. 异常处理
添加全局异常处理器,处理消息发送失败、连接异常等场景,提升系统稳定性。
五、总结
本文介绍了Spring Boot中实现WebSocket的两种核心方式:基于原生API适用于简单点对点通信,配置简单、轻量;基于STOMP协议适用于复杂订阅/发布场景,提供标准化的消息机制,开发效率更高。实际项目中,可根据业务需求选择合适的实现方式。此外,还需关注权限控制、连接管理、兼容性等问题,确保实时通信功能的稳定可靠。
通过Spring Boot的封装,WebSocket开发变得简单高效,开发者无需关注底层协议细节,可专注于业务逻辑实现,为Web应用的实时化升级提供了便捷方案。