杰瑞科技汇

Java如何实现WebSocket?

  1. 使用 Java EE / Jakarta EE 的 API (JSR 356 - Java API for WebSocket):这是 Java 标准自带的 API,无需额外引入核心库,非常规范,如果你的应用服务器(如 Tomcat, Jetty, WildFly)支持 Jakarta EE 8+ 或 Java EE 7+,就可以直接使用。
  2. 使用 Spring Framework 的 WebSocket 支持:这是在 Spring 生态中最常用、功能最强大的方式,它提供了更高级的抽象、STOMP 协议支持、与 Spring Security 的无缝集成以及更简单的配置方式。

下面我将分别详细介绍这两种实现方式,并提供一个完整的、可运行的 Spring Boot 示例。

Java如何实现WebSocket?-图1
(图片来源网络,侵删)

使用 Java EE (JSR 356) 标准 API

这种方式更贴近底层,适合理解 WebSocket 协议本身或在非 Spring 环境下开发。

环境准备

  • JDK 8+
  • 一个支持 WebSocket 的服务器,如 Apache Tomcat 8+Jetty 9+

实现步骤

步骤 1:创建 WebSocket 端点

使用 @ServerEndpoint 注解来定义一个 WebSocket 端点,这个类将处理所有 WebSocket 连接的生命周期事件。

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
// @ServerEndpoint 注解定义了一个WebSocket端点
// value 是连接的URL路径
@ServerEndpoint("/websocket/ee")
public class JavaEeWebSocketEndpoint {
    // 使用一个静态的 Set 来存储所有连接的会话,实现简单的广播功能
    // 注意:在分布式环境下,需要使用更复杂的方式(如Redis Pub/Sub)来管理会话
    private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
    // 当一个新的WebSocket连接打开时调用
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket连接已打开: " + session.getId());
        sessions.add(session);
        // 可以向客户端发送欢迎消息
        try {
            session.getBasicRemote().sendText("欢迎连接到 Java EE WebSocket 服务器!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 当客户端发送消息时调用
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("从客户端 " + session.getId() + " 收到消息: " + message);
        // 回显消息给发送者
        try {
            session.getBasicRemote().sendText("服务器回复: " + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 当WebSocket连接关闭时调用
    @OnClose
    public void onClose(Session session) {
        System.out.println("WebSocket连接已关闭: " + session.getId());
        sessions.remove(session);
    }
    // 当发生错误时调用
    @OnError
    public void onError(Throwable error, Session session) {
        System.err.println("WebSocket发生错误: " + session.getId());
        error.printStackTrace();
        sessions.remove(session);
    }
    // (可选) 一个简单的广播方法
    public static void broadcast(String message) {
        sessions.forEach(session -> {
            try {
                if (session.isOpen()) {
                    session.getBasicRemote().sendText(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

步骤 2:部署到服务器

Java如何实现WebSocket?-图2
(图片来源网络,侵删)

将你的 Web 应用(一个 WAR 文件)部署到 Tomcat 或 Jetty 服务器上。

步骤 3:创建前端 HTML 页面

在 Web 应用的 webapp 目录下创建一个 index.html 文件来测试。

<!DOCTYPE html>
<html>
<head>Java EE WebSocket 客户端</title>
</head>
<body>
    <h2>Java EE WebSocket 聊天</h2>
    <div id="messages"></div>
    <input type="text" id="messageInput">
    <button onclick="sendMessage()">发送</button>
    <script>
        var socket = new WebSocket('ws://' + window.location.host + '/your-webapp-context/websocket/ee');
        socket.onopen = function(event) {
            console.log('连接已建立!');
            document.getElementById('messages').innerHTML += '<p>连接成功!</p>';
        };
        socket.onmessage = function(event) {
            console.log('收到消息: ' + event.data);
            document.getElementById('messages').innerHTML += '<p>' + event.data + '</p>';
        };
        socket.onclose = function(event) {
            console.log('连接已关闭.');
            document.getElementById('messages').innerHTML += '<p>连接已关闭.</p>';
        };
        socket.onerror = function(error) {
            console.error('WebSocket错误: ' + error);
        };
        function sendMessage() {
            var input = document.getElementById('messageInput');
            if (socket.readyState === WebSocket.OPEN) {
                socket.send(input.value);
                input.value = '';
            } else {
                alert('连接未建立,无法发送消息!');
            }
        }
    </script>
</body>
</html>

注意:将 your-webapp-context 替换成你的实际 Web 应用上下文路径。

Java如何实现WebSocket?-图3
(图片来源网络,侵删)

使用 Spring Framework (推荐)

Spring 对 WebSocket 的封装非常出色,配置简单,功能强大,是目前企业级应用开发的首选。

环境准备

  • JDK 8+
  • Spring Boot 2.x (推荐,因为它极大地简化了配置)

实现步骤

步骤 1:添加 Maven 依赖

pom.xml 中添加 spring-boot-starter-websocket 依赖。

<dependencies>
    <!-- Spring Boot WebSocket 支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <!-- 用于构建前端页面的 Thymeleaf 模板引擎 (可选) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

步骤 2:配置 WebSocket

创建一个配置类来启用并注册 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;
@Configuration
@EnableWebSocket // 启用 WebSocket 支持
public class WebSocketConfig implements WebSocketConfigurer {
    // 注入我们自定义的 WebSocket 处理器
    // @Autowired
    // private MyWebSocketHandler myWebSocketHandler;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册 WebSocket 处理器,并指定 WebSocket 端点
        // .setAllowedOrigins("*") 允许所有来源连接,生产环境请根据需要配置
        registry.addHandler(new MyWebSocketHandler(), "/websocket/spring")
                 .setAllowedOrigins("*");
    }
}

步骤 3:创建 WebSocket 处理器

这个类负责处理具体的 WebSocket 事件,类似于 JSR 356 中的 Endpoint 类。

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 {
    // 使用 ConcurrentHashMap 来存储所有连接的会话,线程安全
    private static 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);
        // 向新连接的客户端发送欢迎消息
        session.sendMessage(new TextMessage("欢迎连接到 Spring WebSocket 服务器!"));
    }
    // 收到消息后
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("从客户端 " + session.getId() + " 收到消息: " + payload);
        // 回显消息给发送者
        session.sendMessage(new TextMessage("服务器回复: " + payload));
        // (可选) 广播消息给所有连接的客户端
        // broadcast("广播消息: " + payload);
    }
    // 连接关闭后
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("连接关闭: " + session.getId() + ", 原因: " + status);
        sessions.remove(session.getId());
    }
    // (可选) 广播方法
    public void broadcast(String message) {
        sessions.forEach((id, session) -> {
            try {
                if (session.isOpen()) {
                    session.sendMessage(new TextMessage(message));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

步骤 4:创建前端页面

src/main/resources/templates 目录下创建 index.html (如果使用了 Thymeleaf)。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>Spring WebSocket 客户端</title>
</head>
<body>
    <h2>Spring WebSocket 聊天</h2>
    <div id="messages"></div>
    <input type="text" id="messageInput">
    <button onclick="sendMessage()">发送</button>
    <script th:inline="javascript">
        // 获取当前应用的上下文路径
        var contextPath = /*[[${#request.contextPath}]]*/ '';
        // 构建完整的 WebSocket URL
        var socket = new WebSocket('ws://' + window.location.host + contextPath + '/websocket/spring');
        socket.onopen = function(event) {
            console.log('连接已建立!');
            document.getElementById('messages').innerHTML += '<p>连接成功!</p>';
        };
        socket.onmessage = function(event) {
            console.log('收到消息: ' + event.data);
            document.getElementById('messages').innerHTML += '<p>' + event.data + '</p>';
        };
        socket.onclose = function(event) {
            console.log('连接已关闭.');
            document.getElementById('messages').innerHTML += '<p>连接已关闭.</p>';
        };
        socket.onerror = function(error) {
            console.error('WebSocket错误: ' + error);
        };
        function sendMessage() {
            var input = document.getElementById('messageInput');
            if (socket.readyState === WebSocket.OPEN) {
                socket.send(input.value);
                input.value = '';
            } else {
                alert('连接未建立,无法发送消息!');
            }
        }
    </script>
</body>
</html>

步骤 5:创建主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebSocketApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebSocketApplication.class, args);
    }
}

运行 WebSocketApplication,然后访问 http://localhost:8080,你就可以在浏览器中测试 WebSocket 连接了。


总结与对比

特性 Java EE (JSR 356) Spring Framework
依赖 无需额外核心库,依赖应用服务器 需要 spring-boot-starter-websocket
配置 通过注解直接在类上配置 需要一个独立的 @Configuration
易用性 较为底层,需要手动管理会话集 Spring 自动管理,集成度高,非常方便
功能 标准功能,支持点对点和广播 标准功能 + STOMP 协议支持、Socks5、与 Spring Security/集成等高级特性
适用场景 学习协议、轻量级应用、非 Spring 项目 强烈推荐,任何基于 Spring Boot/Spring MVC 的项目,尤其是需要复杂功能(如聊天室、实时通知)的项目

对于新的项目,尤其是使用 Spring Boot 的项目,强烈推荐使用方式二(Spring Framework),它提供了更优雅的编程模型、更强大的功能和更好的可维护性,Java EE API 虽然标准,但在实际开发中显得有些“原始”。

分享:
扫描分享到社交APP
上一篇
下一篇