- 后端: Spring Boot + Spring WebSocket + SockJS (为了兼容性) + Stomp (用于消息路由)
- 前端: HTML + JavaScript (使用
sockjs-client和stompjs库)
项目结构
websocket-chat/
├── src/
│ ├── main/
│ │ ├── java/com/example/demo/
│ │ │ ├── config/
│ │ │ │ └── WebSocketConfig.java // WebSocket 配置类
│ │ │ ├── controller/
│ │ │ │ └── GreetingController.java // 处理 HTTP 请求
│ │ │ ├── model/
│ │ │ │ └── ChatMessage.java // 聊天消息的数据模型
│ │ │ └── WebSocketChatApplication.java // Spring Boot 启动类
│ │ └── resources/
│ │ ├── static/
│ │ │ └── index.html // 前端聊天页面
│ │ └── templates/
│ └── test/
└── pom.xml // Maven 依赖管理
第 1 步:创建 Spring Boot 项目并添加依赖
使用 Spring Initializr 创建项目,或手动添加以下依赖到你的 pom.xml 文件。

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version> <!-- 使用一个较新的稳定版本 -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring WebSocket 和 Stomp 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Thymeleaf 模板引擎,用于渲染 HTML -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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 步:后端实现
1. 数据模型
创建一个简单的 ChatMessage 类来表示聊天消息。
src/main/java/com/example/demo/model/ChatMessage.java
package com.example.demo.model;
public class ChatMessage {
private String type; // 'JOIN', 'LEAVE', 'CHAT'
private sender: String;
private content: String;
private timestamp: long;
// Getters and Setters
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getSender() { return sender; }
public void setSender(String sender) { this.sender = sender; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
2. WebSocket 配置
这是核心配置,它启用 STOMP 并注册一个端点。

src/main/java/com/example/demo/config/WebSocketConfig.java
package com.example.demo.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;
@Configuration
@EnableWebSocketMessageBroker // 启用 WebSocket 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启用一个简单的内存消息代理,用于处理以 "/topic" 开头的目的地
config.enableSimpleBroker("/topic");
// 设置应用程序目的地前缀,带有 @MessageMapping 注解的方法的映射地址将以它开头
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册一个 STOMP 端点,客户端将连接到这个端点
// withSockJS() 允许在不支持 WebSocket 的浏览器上使用回退机制(如 long-polling)
registry.addEndpoint("/ws").withSockJS();
}
}
3. 消息控制器
这个控制器处理客户端发送的消息和订阅请求。
src/main/java/com/example/demo/controller/GreetingController.java
package com.example.demo.controller;
import com.example.demo.model.ChatMessage;
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.stereotype.Controller;
@Controller
public class GreetingController {
// @MessageMapping: 映射到 "/app/chat.sendMessage" (因为我们在配置中设置了前缀 "/app")
// @SendTo: 将方法的返回值发送给所有订阅了 "/topic/public" 的客户端
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
// @MessageMapping: 映射到 "/app/chat.addUser"
// @SendTo: 将方法的返回值发送给所有订阅了 "/topic/public" 的客户端
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage,
SimpMessageHeaderAccessor headerAccessor) {
// 添加用户到 WebSocket 会话中
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
return chatMessage;
}
}
4. HTTP 控制器 (可选,但推荐)
创建一个简单的控制器来提供聊天页面。

src/main/java/com/example/demo/controller/ChatController.java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ChatController {
@GetMapping("/chat")
public String chat() {
return "index"; // 返回 Thymeleaf 模板 "index.html"
}
}
第 3 步:前端实现
1. 添加前端库
在你的 index.html 中引入 sockjs 和 stompjs 库,最简单的方式是使用 CDN。
src/main/resources/static/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">Spring WebSocket Chat</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#chat-container { border: 1px solid #ccc; padding: 10px; height: 400px; overflow-y: scroll; }
#message-form { margin-top: 10px; }
#message-input { width: 70%; padding: 5px; }
button { padding: 5px 10px; }
.message { margin-bottom: 5px; }
.system-message { color: #888; font-style: italic; }
</style>
</head>
<body>
<h2>Spring WebSocket Chat</h2>
<div id="chat-container"></div>
<form id="message-form">
<input type="text" id="message-input" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
<!-- 引入 SockJS 和 Stomp 客户端库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script th:src="@{/js/chat.js}"></script> <!-- 引入我们自己的 JS 文件 -->
</body>
</html>
2. 编写前端 JavaScript 逻辑
创建一个 js 目录和 chat.js 文件。
src/main/resources/static/js/chat.js
document.addEventListener('DOMContentLoaded', function() {
const chatContainer = document.getElementById('chat-container');
const messageForm = document.getElementById('message-form');
const messageInput = document.getElementById('message-input');
let stompClient = null;
function connect() {
// 1. 建立 SockJS 连接
const socket = new SockJS('/ws');
// 2. 使用 Stomp 协议 over SockJS
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
// 3. 订阅公共聊天频道
stompClient.subscribe('/topic/public', function (message) {
showMessage(JSON.parse(message.body));
});
// 发送加入消息
const joinMessage = {
sender: 'User', // 可以在这里动态获取用户名,例如从 prompt 输入
type: 'JOIN',
content: 'has joined the chat!',
timestamp: new Date().getTime()
};
stompClient.send("/app/chat.addUser", {}, JSON.stringify(joinMessage));
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
function sendMessage(event) {
event.preventDefault(); // 阻止表单默认提交行为
const messageContent = messageInput.value.trim();
if (messageContent && stompClient) {
const chatMessage = {
sender: 'User', // 同上,可以动态设置
type: 'CHAT',
content: messageContent,
timestamp: new Date().getTime()
};
// 发送消息到 /app/chat.sendMessage
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
messageInput.value = ''; // 清空输入框
}
}
function showMessage(message) {
const messageElement = document.createElement('div');
messageElement.classList.add('message');
const timestamp = new Date(message.timestamp).toLocaleTimeString();
if (message.type === 'JOIN' || message.type === 'LEAVE') {
messageElement.classList.add('system-message');
messageElement.textContent = `[${timestamp}] ${message.sender} ${message.content}`;
} else {
messageElement.textContent = `[${timestamp}] ${message.sender}: ${message.content}`;
}
chatContainer.appendChild(messageElement);
chatContainer.scrollTop = chatContainer.scrollHeight; // 滚动到底部
}
// 事件监听
messageForm.addEventListener('submit', sendMessage);
// 连接 WebSocket
connect();
});
第 4 步:运行和测试
-
运行后端: 在你的 IDE 中运行
WebSocketChatApplication.java,或者使用 Maven 命令:mvn spring-boot:run
-
打开前端: 在浏览器中访问
http://localhost:8080/chat。 -
测试:
- 打开两个或更多的浏览器窗口,都访问
http://localhost:8080/chat。 - 在任意一个窗口中输入消息并点击 "Send"。
- 你应该能看到消息实时地出现在所有打开的窗口的聊天记录中。
- 当页面加载时,会看到 "User has joined the chat!" 的系统消息,当关闭页面时,WebSocket 连接会断开,但为了更好的体验,你可以在
chat.js的disconnect函数中发送一个 "LEAVE" 消息。
- 打开两个或更多的浏览器窗口,都访问
总结与扩展
- 用户名: 当前代码中硬编码了用户名为 "User",在实际应用中,你应该在用户登录后,将用户名存储在会话中,并在连接 WebSocket 时传递过去。
- 房间/频道: 当前实现的是公共聊天室,你可以轻松扩展为私聊或多个房间。
- 私聊: 在
ChatMessage中增加recipient字段,在GreetingController中,使用@SendToUser("/queue/private")来发送消息给特定用户。 - 房间: 在
ChatMessage中增加room字段,在GreetingController中,将@SendTo("/topic/public")改为@SendTo("/topic/" + chatMessage.getRoom())。
- 私聊: 在
- 安全性: 考虑使用 Spring Security 来保护你的 WebSocket 端点,确保只有认证用户才能连接和发送消息。
这个例子为你提供了一个功能齐全的、可扩展的 Java WebSocket 聊天应用的基础,你可以基于此进行更多功能的开发。
