- Java EE (Jakarta EE) / Jakarta EE 方式:这是 Java 官方提供的标准 API,在 Jakarta EE 9+ (原 Java EE) 容器(如 Tomcat 10+, WildFly, Payara)中内置支持,这是最标准、最推荐的方式,尤其适合构建企业级应用。
- Spring Boot 方式:在 Spring Boot 框架中实现 WebSocket,通过
spring-boot-starter-websocket提供了非常便捷和强大的集成,是目前在微服务和新项目中非常流行的选择。
使用 Java EE (Jakarta EE) API
这种方式的核心是创建一个 Endpoint 类来处理 WebSocket 连接的生命周期事件。

环境准备
你需要一个支持 Jakarta EE 9+ 的 Web 服务器,
- Apache Tomcat 10+
- WildFly 26+
- Payara 6+
创建 WebSocket 端点
创建一个 Java 类,继承 jakarta.websocket.Endpoint 并重写其生命周期方法。
// package com.example.websocket;
import jakarta.websocket.*;
import java.io.IOException;
@ServerEndpoint("/echo") // 定义 WebSocket 的访问路径
public class EchoEndpoint {
// 当一个新的 WebSocket 连接建立时被调用
@OnOpen
public void onOpen(Session session) {
System.out.println("连接已建立,Session ID: " + session.getId());
// 可以在这里将 session 存储起来,以便后续向特定客户端发送消息
}
// 当客户端发送消息时被调用
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("收到来自客户端 " + session.getId() + " 的消息: " + message);
// 将收到的消息回显给客户端
session.getBasicRemote().sendText("服务器回显: " + message);
}
// 当连接关闭时被调用
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("连接已关闭,Session ID: " + session.getId() + ", 原因: " + closeReason);
}
// 当连接过程中发生错误时被调用
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("连接发生错误,Session ID: " + session.getId());
throwable.printStackTrace();
}
}
注册 WebSocket 端点
在 Jakarta EE 中,你可以通过一个 ServerApplicationConfig 类来编程式地注册你的 Endpoint,或者在 web.xml 中声明式地注册。编程式注册更灵活,推荐使用。
创建 ServerApplicationConfig 类:

// package com.example.websocket;
import jakarta.websocket.Endpoint;
import jakarta.websocket.server.ServerApplicationConfig;
import jakarta.websocket.server.ServerEndpointConfig;
import java.util.Set;
public class MyWebSocketConfig implements ServerApplicationConfig {
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
// 返回我们自定义的 Endpoint 配置
// 这里我们手动创建并配置我们的 EchoEndpoint
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(
EchoEndpoint.class, // 我们的 Endpoint 类
"/echo" // 访问路径
).build();
return Set.of(config);
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scannedClasses) {
// 如果我们使用 @ServerEndpoint 注解,容器会自动扫描。
// 这里我们返回空集,因为我们上面是手动配置的。
return scannedClasses;
}
}
创建前端页面 (index.html)
在 webapp 目录下创建一个 index.html 文件来测试。
<!DOCTYPE html>
<html>
<head>Java WebSocket 测试</title>
</head>
<body>
<h2>Java WebSocket 聊天测试</h2>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<script>
// 1. 建立 WebSocket 连接
// 注意:ws:// 对应 http, wss:// 对应 https
// window.location.host 可以获取当前主机和端口
const socket = new WebSocket("ws://" + window.location.host + "/echo");
// 2. 监听打开事件
socket.onopen = function(event) {
console.log("WebSocket 连接已打开!");
document.getElementById("messages").innerHTML += "<p>连接成功!</p>";
};
// 3. 监听消息事件
socket.onmessage = function(event) {
console.log("收到服务器消息: " + event.data);
const messagesDiv = document.getElementById("messages");
messagesDiv.innerHTML += "<p>服务器: " + event.data + "</p>";
};
// 4. 监听关闭事件
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`连接已正常关闭,代码=${event.code} 原因=${event.reason}`);
} else {
console.error('连接被异常中断');
}
};
// 5. 监听错误事件
socket.onerror = function(error) {
console.error("WebSocket 错误: " + error);
};
// 发送消息的函数
function sendMessage() {
const input = document.getElementById("messageInput");
const message = input.value;
if (message) {
socket.send(message);
input.value = ""; // 清空输入框
}
}
</script>
</body>
</html>
部署和测试
- 将你的 Java 项目打包成 WAR 文件。
- 部署到 Tomcat 10+ 服务器。
- 启动服务器,访问
http://localhost:8080/你的项目名/。 - 打开浏览器的开发者工具(F12),在 Console 标签页可以看到连接日志。
- 在输入框中输入消息,点击发送,可以看到页面下方显示服务器回显的消息。
使用 Spring Boot
Spring Boot 对 WebSocket 的封装使得配置和使用都非常简单。
环境准备
- 安装 JDK 8+。
- 使用 Spring Initializr (https://start.spring.io/) 创建一个新项目,添加以下依赖:
Spring Web:用于提供 Web 服务。Spring WebSocket:核心 WebSocket 支持。
启用 WebSocket
在你的 Spring Boot 主应用类上添加 @EnableWebSocket 注解。
// package com.example.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@SpringBootApplication
@EnableWebSocket // 启用 WebSocket 支持
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}
}
配置 WebSocket 并创建处理器
创建一个配置类来注册 WebSocket 端点,并创建一个处理器类来处理消息逻辑。

WebSocket 配置类 (WebSocketConfig.java)
// package com.example.websocket.config;
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;
import org.springframework.beans.factory.annotation.Autowired;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private MyWebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册我们的处理器
// .addHandler(处理器实例, "访问路径")
// .setAllowedOrigins("*") 允许所有来源的连接,生产环境请设置具体域名
registry.addHandler(webSocketHandler, "/chat")
.setAllowedOrigins("*");
}
}
WebSocket 处理器类 (MyWebSocketHandler.java)
Spring Boot 推荐使用 WebSocketHandler 接口,它提供了更丰富的回调方法。
// package com.example.websocket;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
// 使用一个线程安全的 Map 来存储所有在线的会话
private final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
// 连接建立后
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("新连接建立: " + session.getId());
sessions.put(session.getId(), session);
// 可以在这里广播新用户加入的消息
broadcastMessage("用户 " + session.getId() + " 加入了聊天室。");
}
// 收到文本消息后
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到来自 " + session.getId() + " 的消息: " + message.getPayload());
// 广播消息给所有连接的客户端
broadcastMessage("用户 " + session.getId() + " 说: " + message.getPayload());
}
// 连接关闭后
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接关闭: " + session.getId() + ", 原因: " + status);
sessions.remove(session.getId());
// 广播用户离开的消息
broadcastMessage("用户 " + session.getId() + " 离开了聊天室。");
}
// 广播消息的辅助方法
private void broadcastMessage(String message) {
TextMessage textMessage = new TextMessage(message);
sessions.forEach((id, session) -> {
try {
if (session.isOpen()) {
session.sendMessage(textMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
创建前端页面 (index.html)
前端代码与方案一中的 index.html 几乎完全相同,只需要确保 WebSocket 的连接地址是 /chat。
<!DOCTYPE html>
<html>
<head>Spring WebSocket 测试</title>
<style>
#messages { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; }
#messageInput { width: 80%; }
</style>
</head>
<body>
<h2>Spring WebSocket 聊天室</h2>
<div id="messages"></div>
<br>
<input type="text" id="messageInput" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<script>
const socket = new WebSocket("ws://" + window.location.host + "/chat");
socket.onopen = function(event) {
console.log("WebSocket 连接已打开!");
appendMessage("系统:连接成功!");
};
socket.onmessage = function(event) {
console.log("收到服务器消息: " + event.data);
appendMessage(event.data);
};
socket.onclose = function(event) {
console.log(`连接已关闭: ${event.code} ${event.reason}`);
appendMessage("系统:连接已关闭。");
};
socket.onerror = function(error) {
console.error("WebSocket 错误: " + error);
appendMessage("系统:发生连接错误。");
};
function sendMessage() {
const input = document.getElementById("messageInput");
const message = input.value;
if (message) {
socket.send(message);
input.value = "";
}
}
function appendMessage(message) {
const messagesDiv = document.getElementById("messages");
const p = document.createElement("p");
p.textContent = message;
messagesDiv.appendChild(p);
messagesDiv.scrollTop = messagesDiv.scrollHeight; // 自动滚动到底部
}
// 监听回车键发送
document.getElementById("messageInput").addEventListener("keypress", function(event) {
if (event.key === "Enter") {
sendMessage();
}
});
</script>
</body>
</html>
运行和测试
- 运行
WebSocketApplication.java的main方法。 - 打开浏览器访问
http://localhost:8080。 - 打开两个或更多浏览器窗口,都访问同一个地址。
- 在一个窗口输入消息并发送,你会看到所有窗口都收到了这条广播消息。
总结与对比
| 特性 | Java EE (Jakarta EE) API | Spring Boot |
|---|---|---|
| 核心思想 | 标准 API,基于 Endpoint 和生命周期注解 (@OnOpen, @OnMessage 等) |
基于 WebSocketHandler 接口,更面向对象和 Spring 风格 |
| 配置方式 | 编程式 (ServerApplicationConfig) 或声明式 (web.xml) |
通过 @Configuration 类和 WebSocketConfigurer 接口 |
| 状态管理 | 需要自己用 ConcurrentHashMap 等工具管理 Session |
同样需要自己管理,但与 Spring 的其他部分(如 Security)集成更方便 |
| 优点 | 标准、规范,与 Java EE 容器深度集成,无额外依赖 | 配置简单、集成度高,与 Spring 生态(Security, MVC等)无缝协作,开发效率高 |
| 缺点 | 配置略显繁琐,功能相对基础 | 引入了 Spring 框架,对于简单的 WebSocket 应用可能显得“重” |
| 适用场景 | 传统 Java EE 项目,需要遵循标准规范的场景 | 新项目,特别是基于 Spring Boot 或 Spring Cloud 的微服务架构 |
如何选择?
- 如果你的项目已经是基于 Jakarta EE / Java EE 的,或者你希望遵循最标准、最官方的实现方式,选择方案一。
- 如果你正在构建一个新的项目,特别是使用 Spring Boot 框架,或者希望获得更快的开发速度和更好的集成体验,强烈推荐方案二,它是目前社区的主流选择。
