- 核心概念简介:WebSocket 是什么,为什么用它。
- 项目搭建:使用 Spring Initializr 创建项目。
- 后端实现:创建 WebSocket 端点、处理消息、广播消息。
- 前端实现:创建聊天界面,连接 WebSocket,发送和接收消息。
- 测试与运行:启动应用,体验聊天功能。
核心概念简介
HTTP vs. WebSocket

- HTTP (请求-响应模式):客户端必须先发起请求,服务器才能响应,服务器无法主动向客户端推送信息,这对于需要实时更新的应用(如聊天室、股票行情)效率很低,通常需要轮询(Polling)或长轮询(Long Polling),这会造成不必要的网络开销和延迟。
- WebSocket (全双工通信):它是一个在单个 TCP 连接上进行全双工通信的协议,一旦连接建立,客户端和服务器就可以随时相互发送数据,无需一方先发起请求,这为实时应用提供了高效、低延迟的解决方案。
Spring WebSocket 架构
Spring 对 WebSocket 提供了非常优雅的支持,其核心组件包括:
@ServerEndpoint(标准 Java API):如果使用标准 Java WebSocket API,可以用这个注解来定义一个 WebSocket 端点。WebSocketMessageBroker(Spring 方式):这是 Spring 推荐的方式,它基于消息代理,更加灵活和强大,它引入了“消息代理”(Message Broker)的概念,服务器和客户端都通过消息代理来收发消息,而不是直接点对点,这使得构建如聊天室(广播)、私信(点对点)等场景变得更加简单。
在本教程中,我们将使用更简单、更直接的 @ServerEndpoint 方式,因为它更容易理解 WebSocket 的基本工作原理。
项目搭建
- 访问 Spring Initializr。
- 填写项目信息:
- Project: Maven Project
- Language: Java
- Spring Boot: 选择一个较新的稳定版本 (如 3.x.x)
- Project Metadata:
- Group:
com.example - Artifact:
websocket-chat - Name:
websocket-chat - Description: Demo project for WebSocket Chat
- Package name:
com.example.websocketchat
- Group:
- Dependencies: 添加以下依赖
Spring Web: 用于提供 Web 服务。WebSocket: 这是核心依赖,提供了 WebSocket 支持。
- 点击 "GENERATE" 下载项目压缩包,并用你的 IDE (如 IntelliJ IDEA 或 Eclipse) 打开。
下载后,你的 pom.xml 文件会自动包含 spring-boot-starter-websocket 依赖。

后端实现
我们将创建一个 WebSocket 端点来处理连接、断开连接、接收和发送消息。
1 创建 WebSocket 端点类
在 src/main/java/com/example/websocketchat 目录下创建一个新类 ChatEndpoint.java。
package com.example.websocketchat;
import jakarta.websocket.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@Component // 使用 @Component 让 Spring 管理
@ServerEndpoint("/chat") // 定义 WebSocket 端点路径
public class ChatEndpoint {
// 使用 Set 来存储所有连接的会话,实现广播功能
// 使用 Collections.synchronizedSet 保证线程安全
private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
private static final Logger logger = LoggerFactory.getLogger(ChatEndpoint.class);
// 当一个新的 WebSocket 连接建立时被调用
@OnOpen
public void onOpen(Session session) {
chatroomUsers.add(session);
logger.info("新用户连接: {}, 当前在线人数: {}", session.getId(), chatroomUsers.size());
// 广播新用户加入的消息
broadcastMessage("系统: 用户 " + session.getId() + " 加入了聊天室。");
}
// 当客户端发送消息时被调用
@OnMessage
public void onMessage(String message, Session session) {
logger.info("来自用户 {} 的消息: {}", session.getId(), message);
// 广播消息给所有用户
broadcastMessage("用户 " + session.getId() + ": " + message);
}
// 当 WebSocket 连接关闭时被调用
@OnClose
public void onClose(Session session) {
chatroomUsers.remove(session);
logger.info("用户断开连接: {}, 当前在线人数: {}", session.getId(), chatroomUsers.size());
// 广播用户离开的消息
broadcastMessage("系统: 用户 " + session.getId() + " 离开了聊天室。");
}
// 当 WebSocket 连接发生错误时被调用
@OnError
public void onError(Throwable error, Session session) {
logger.error("用户 {} 发生错误: {}", session.getId(), error.getMessage());
chatroomUsers.remove(session);
}
// 广播消息给所有连接的客户端
private void broadcastMessage(String message) {
// 同步遍历,防止在遍历时集合被修改
synchronized (chatroomUsers) {
for (Session session : chatroomUsers) {
if (session.isOpen()) {
try {
// getBasicRemote() 是同步的,适合简单场景
session.getBasicRemote().sendText(message);
} catch (IOException e) {
logger.error("向用户 {} 发送消息失败", session.getId(), e);
// 如果发送失败,可能连接已断开,从集合中移除
chatroomUsers.remove(session);
}
}
}
}
}
}
代码解释:
@Component: 告诉 Spring 这是一个 Spring Bean,需要被管理。@ServerEndpoint("/chat"): 将这个类声明为一个 WebSocket 端点,客户端可以通过ws://your-host:your-port/chat来连接。@OnOpen,@OnMessage,@OnClose,@OnError: 这些是生命周期方法注解,分别对应连接建立、收到消息、连接关闭和发生错误。Set<Session> chatroomUsers: 这是一个静态的、线程安全的集合,用来保存所有当前连接的客户端会话,这是实现聊天室广播功能的关键。broadcastMessage(String message): 这个方法遍历所有连接的会话,并向每个会话发送消息。
2 启用 WebSocket 支持
为了让 Spring Boot 应用识别并启用我们的 WebSocket 端点,我们需要创建一个配置类。

在 src/main/java/com/example/websocketchat 目录下创建 WebSocketConfig.java。
package com.example.websocketchat;
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 // 启用 WebSocket 功能
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册我们自定义的 ChatEndpoint
// .setAllowedOrigins("*") 允许所有来源的连接,适合开发。
// 在生产环境中,应该指定具体的域名,如 "http://localhost:8080"
registry.addHandler(new ChatEndpoint(), "/chat")
.setAllowedOrigins("*");
}
}
代码解释:
@Configuration: 表明这是一个配置类。@EnableWebSocket: 启用 Spring WebSocket 功能。WebSocketConfigurer: 实现这个接口来配置 WebSocket。registerWebSocketHandlers: 在这个方法中,我们将ChatEndpoint注册到/chat路径上。setAllowedOrigins("*")是为了允许前端页面(可能运行在不同的端口或域名上)连接到后端 WebSocket。
前端实现
现在我们来创建一个简单的 HTML 页面作为聊天客户端。
在 src/main/resources/static 目录下创建一个 index.html 文件。static 目录不存在,请手动创建。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">WebSocket 聊天室</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#chat-box {
border: 1px solid #ccc;
padding: 10px;
height: 400px;
overflow-y: scroll;
margin-bottom: 10px;
background-color: #f9f9f9;
}
#message-input {
width: 70%;
padding: 8px;
}
#send-button {
width: 25%;
padding: 8px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
#send-button:hover {
background-color: #0056b3;
}
.message {
margin-bottom: 5px;
padding: 5px;
border-radius: 5px;
}
.system-message {
color: #888;
font-style: italic;
}
.user-message {
color: #333;
}
</style>
</head>
<body>
<h2>WebSocket 聊天室</h2>
<div id="chat-box"></div>
<div>
<input type="text" id="message-input" placeholder="输入消息...">
<button id="send-button">发送</button>
</div>
<script>
// 1. 获取 DOM 元素
const chatBox = document.getElementById('chat-box');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
// 2. 创建 WebSocket 连接
// 注意:这里的 URL 必须和后端 @ServerEndpoint 的路径一致
// 使用 ws:// (非加密) 或 wss:// (加密)
const ws = new WebSocket("ws://localhost:8080/chat");
// 3. 监听 WebSocket 事件
// 连接成功时触发
ws.onopen = function(event) {
console.log("WebSocket 连接成功!");
appendMessage("系统: 你已成功连接到聊天室。", "system-message");
};
// 收到消息时触发
ws.onmessage = function(event) {
console.log("收到消息: " + event.data);
appendMessage(event.data, "user-message");
};
// 连接关闭时触发
ws.onclose = function(event) {
console.log("WebSocket 连接已关闭。");
appendMessage("系统: 你与聊天室的连接已断开。", "system-message");
};
// 发生错误时触发
ws.onerror = function(error) {
console.error("WebSocket 错误: " + error);
appendMessage("系统: 发生连接错误。", "system-message");
};
// 4. 发送消息
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
ws.send(message); // 通过 WebSocket 发送消息
messageInput.value = ""; // 清空输入框
}
}
// 绑定发送按钮的点击事件
sendButton.onclick = sendMessage;
// 绑定输入框的回车事件
messageInput.onkeypress = function(event) {
if (event.key === "Enter") {
sendMessage();
}
};
// 5. 将消息添加到聊天框
function appendMessage(message, className) {
const messageElement = document.createElement("div");
messageElement.classList.add("message", className);
messageElement.textContent = message;
chatBox.appendChild(messageElement);
// 滚动到底部
chatBox.scrollTop = chatBox.scrollHeight;
}
</script>
</body>
</html>
代码解释:
new WebSocket("ws://localhost:8080/chat"): 创建一个 WebSocket 连接,URL 指向后端定义的端点。ws.onopen,ws.onmessage,ws.onclose,ws.onerror: JavaScript 的 WebSocket API 提供的事件监听器,分别对应连接生命周期。ws.send(message): 向服务器发送消息。appendMessage: 一个辅助函数,用于将接收到的消息格式化并显示在页面上。
测试与运行
-
运行后端:
- 在你的 IDE 中,找到
WebSocketChatApplication.java(这是 Spring Boot 的主启动类)。 - 右键点击
Run,应用将在8080端口启动。
- 在你的 IDE 中,找到
-
测试聊天:
- 打开浏览器 (如 Chrome),访问
http://localhost:8080,由于我们放在了static目录下,Spring Boot 会自动提供index.html。 - 打开第二个浏览器窗口或标签页,同样访问
http://localhost:8080。 - 在任意一个窗口中输入消息并点击“发送”或按回车。
- 你会看到消息立即显示在两个窗口的聊天框中。
- 当你关闭一个窗口时,另一个窗口会收到“用户离开”的系统消息。
- 打开浏览器 (如 Chrome),访问
总结与进阶
你已经成功构建了一个基础的 Java WebSocket 聊天应用!
这个例子的关键点:
- 后端: 使用
@ServerEndpoint定义端点,用Set<Session>管理所有连接,并通过session.getBasicRemote().sendText()实现广播。 - 前端: 使用
new WebSocket()创建连接,并通过onmessage事件监听服务器推送的消息。
可以进一步改进的方向:
- 用户身份认证: 当前用户只是一个
session.getId(),你可以结合 Spring Security,在@OnOpen时从 HTTP Session 或 Token 中获取用户名,并在消息中显示。 - 消息持久化: 将聊天记录保存到数据库(如 MySQL, PostgreSQL)中,这样用户刷新页面后还能看到历史记录。
- 私聊功能: 修改
broadcastMessage方法,增加一个toUserId参数,只将消息发送给指定的用户。 - 使用 STOMP 协议: 对于更复杂的场景(如需要消息队列、订阅/发布模式),可以使用 Spring 的 STOMP (Simple Text Oriented Messaging Protocol) over WebSocket,它提供了消息路由功能,比原始的 WebSocket 更强大。
- 前端框架集成: 将聊天功能集成到 Vue, React 或 Angular 等现代前端框架中,构建更复杂的用户界面。
