Spring Boot 如何集成使用 WebSocket

3,876 阅读5分钟

在 Spring Boot 中集成 WebSocket 可以通过 spring-boot-starter-websocket 依赖实现。WebSocket 允许服务器和客户端进行全双工通信,适用于聊天系统、实时通知等应用场景。


  1. 添加依赖

pom.xml 文件中添加 WebSocket 相关的 Spring Boot 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

  1. 配置 WebSocket

在 Spring Boot 中,需要创建一个 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
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws")
                .setAllowedOrigins("*"); // 允许所有来源,生产环境需指定域名
    }
}
  • @EnableWebSocket:启用 WebSocket 支持。
  • registerWebSocketHandlers():注册 WebSocket 处理器。
  • setAllowedOrigins("*"):允许跨域访问,生产环境应限制来源。

  1. 编写 WebSocket 处理器

创建一个 MyWebSocketHandler,用于处理 WebSocket 连接、消息接收和关闭事件。

import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
public class MyWebSocketHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("新连接建立: " + session.getId());
        session.sendMessage(new TextMessage("欢迎连接 WebSocket 服务器!"));
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
        session.sendMessage(new TextMessage("服务器收到: " + message.getPayload()));
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("连接关闭: " + session.getId());
    }
}
  • afterConnectionEstablished():客户端连接建立时调用,可发送欢迎消息。
  • handleTextMessage():接收客户端发送的文本消息,并返回相同的内容。
  • afterConnectionClosed():连接关闭时执行清理操作。

  1. 前端测试 WebSocket

使用 JavaScript 在浏览器中测试 WebSocket:

const socket = new WebSocket("ws://localhost:8080/ws");
socket.onopen = function () {
    console.log("WebSocket 连接已打开");
    socket.send("Hello Server!");
};
socket.onmessage = function (event) {
    console.log("收到服务器消息: " + event.data);
};
socket.onclose = function () {
    console.log("WebSocket 连接已关闭");
};

  1. 运行和测试

  • 启动 Spring Boot 应用,确保 WebSocket 监听 /ws 路径。
  • 在浏览器控制台执行 JavaScript 代码,查看 WebSocket 通信效果。

  1. 使用 @ServerEndpoint 实现 WebSocket(基于 javax.websocket

如果需要基于 @ServerEndpoint 方式,可以使用 Spring Boot 提供的 spring-boot-starter-websocket,但需要引入 javax.websocket-api

  1.   添加依赖

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>
  1.   WebSocket 服务器端

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/ws")
public class WebSocketServer {
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
    private Session session;
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSockets.add(this);
        System.out.println("新连接加入,总连接数: " + webSockets.size());
    }
    @OnMessage
    public void onMessage(String message) throws IOException {
        System.out.println("收到消息: " + message);
        sendMessage("服务器响应: " + message);
    }
    @OnClose
    public void onClose() {
        webSockets.remove(this);
        System.out.println("连接关闭,总连接数: " + webSockets.size());
    }
    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("WebSocket 错误: " + error.getMessage());
    }
    public void sendMessage(String message) throws IOException {
        for (WebSocketServer ws : webSockets) {
            ws.session.getBasicRemote().sendText(message);
        }
    }
}
  1.   启用 @ServerEndpoint

  由于 Spring Boot 默认不支持 @ServerEndpoint,需要手动注册:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.websocket.server.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

  1. 总结
方式说明适用场景
WebSocketHandler(推荐)使用 WebSocketHandler 和 WebSocketConfigurer 配置适用于 Spring Boot 原生支持
@ServerEndpoint(基于 Javax)直接在类上使用 @ServerEndpoint 注解适用于 Java EE 标准 WebSocket 方式

  如果你的 Spring Boot 项目是Spring WebFlux,则可以使用 @EnableWebSocket 配合 reactor-netty 进行 WebSocket 处理。

  你更倾向于哪种方式?


  1. 业务中实践

在实际业务中,WebSocket 通常用于 实时通信 场景,例如:

  • 在线聊天(单聊、群聊)
  • 消息推送(系统通知、警报)
  • 实时数据更新(股票、天气、订单状态)
  • 协同编辑(在线文档、多人游戏)

接下来,我会基于 WebSocketHandler 方式,举例说明如何在 Spring Boot 业务中应用 WebSocket


  1. 需求:实现实时消息推送

假设你有一个电商后台系统,需要给用户推送订单状态更新消息,WebSocket 服务器负责:

  1. 客户端(用户)连接 WebSocket,并提供 userId 进行身份验证。
  2. 服务器端存储用户的 WebSocket 连接,并在有订单状态更新时推送消息。
  3. 断开连接时,清理 WebSocket 连接。

  1. 代码实现
  1. 维护 WebSocket 连接

创建一个 WebSocketSessionManager,用于存储在线用户的 WebSocket 连接,并提供消息推送能力。

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
public class WebSocketSessionManager {
    // 存储 WebSocket 连接,key 为 userId
    private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
    // 添加用户连接
    public static void addSession(String userId, WebSocketSession session) {
        sessions.put(userId, session);
    }
    // 移除用户连接
    public static void removeSession(String userId) {
        sessions.remove(userId);
    }
    // 发送消息
    public static void sendMessage(String userId, String message) {
        WebSocketSession session = sessions.get(userId);
        if (session != null && session.isOpen()) {
            try {
                session.sendMessage(new TextMessage(message));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  1. WebSocket 处理器

MyWebSocketHandler 里,处理用户连接、消息接收和断开连接。

import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Map;
public class MyWebSocketHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 从请求参数获取 userId,例如 ws://localhost:8080/ws?userId=123
        Map<String, String> params = session.getAttributes();
        String userId = (String) session.getAttributes().get("userId");
        if (userId != null) {
            WebSocketSessionManager.addSession(userId, session);
            session.sendMessage(new TextMessage("连接成功,您的 ID: " + userId));
        } else {
            session.close();
        }
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
        session.sendMessage(new TextMessage("服务器收到:" + message.getPayload()));
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        if (userId != null) {
            WebSocketSessionManager.removeSession(userId);
        }
        System.out.println("用户 " + userId + " 断开连接");
    }
}

  1. WebSocket 配置

注册 WebSocket 端点,并支持 userId 作为参数。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws")
                .setAllowedOrigins("*")
                .addInterceptors(new WebSocketHandshakeInterceptor());
    }
}

  1. 解析 userId

创建 WebSocketHandshakeInterceptor,用于解析 userId 并存入 session

import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(
            org.springframework.http.server.ServerHttpRequest request,
            org.springframework.http.server.ServerHttpResponse response,
            WebSocketHandler wsHandler,
            Map<String, Object> attributes) {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            String userId = servletRequest.getServletRequest().getParameter("userId");
            if (userId != null) {
                attributes.put("userId", userId);
                return true;
            }
        }
        return false;
    }
    @Override
    public void afterHandshake(org.springframework.http.server.ServerHttpRequest request,
                               org.springframework.http.server.ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {
    }
}

  1. 业务中如何推送消息

在 Spring 业务代码(如订单更新服务)中,调用 WebSocketSessionManager.sendMessage() 发送通知。

示例:订单状态更新时推送 WebSocket 消息

import org.springframework.stereotype.Service;
@Service
public class OrderService {
    public void updateOrderStatus(String orderId, String userId, String status) {
        // 更新数据库订单状态(省略数据库操作)
        System.out.println("订单 " + orderId + " 状态变更为:" + status);
        // WebSocket 推送通知
        WebSocketSessionManager.sendMessage(userId, "您的订单 " + orderId + " 状态更新为:" + status);
    }
}

  1. 客户端测试

可以使用 JavaScript 进行 WebSocket 测试:

const userId = "123"; // 假设用户 ID 为 123
const socket = new WebSocket(
ws://localhost:8080/ws?userId=${userId}
);
socket.onopen = function () {
    console.log("WebSocket 连接成功");
    socket.send("Hello Server!");
};
socket.onmessage = function (event) {
    console.log("收到服务器消息: " + event.data);
};
socket.onclose = function () {
    console.log("WebSocket 连接已关闭");
};

  1. 运行和测试

  1. 启动 Spring Boot 应用
  2. 在浏览器打开控制台,运行 JavaScript 代码
  3. 在后端调用 OrderService.updateOrderStatus("1001", "123", "已发货")
  4. 前端 WebSocket 收到推送消息

  1. 代码总结

  • WebSocketSessionManager:管理 WebSocket 连接并推送消息
  • MyWebSocketHandler:WebSocket 事件处理(连接、消息、断开)
  • WebSocketConfig:注册 WebSocket 端点
  • WebSocketHandshakeInterceptor:从请求中解析 userId
  • OrderService:业务层调用 WebSocketSessionManager.sendMessage() 发送通知

  1. 适用场景

消息推送(订单状态、系统通知) ✅ 在线聊天(可以扩展 sessions 维护群聊) ✅ 实时数据更新(股票、天气、物流跟踪)

如果需要 群聊,可以修改 WebSocketSessionManager 存储多个用户,并在 sendMessage() 方法中向所有用户发送消息。