Spring Boot 整合 WebSocket 实现 WS 通信的完整步骤详解

来源:SEO作者:上海网站建设头衔:草根站长
导读:本期聚焦于上海网站建设创作的《Spring Boot 整合 WebSocket 实现 WS 通信的完整步骤详解》,敬请观看详情。为什么传统的 HTTP 请求难以满足服务端主动推送数据的需求?WebSocket 提供的全双工通信机制正好解决了这个痛点。本文将围绕 Spring Boot 环境下整合 WebSocket 展开讲解,内容涵盖原生 WebSocket 方式的依赖引入与配置类编写、@ServerEndpoint 注解的使用细节、心跳检测与多实例部署时的 Session 共享问题,以及基于 STOMP 协议和 SockJS 的进阶方案。文中配有完整的依赖配置、服务端代码和前端连接示例,并分析了连接断开、跨域拦截等常见坑点,帮助你快速搭建稳定可用的 WS 通信服务。

在许多实时性要求较高的业务场景里,比如在线客服、股票行情推送、协同编辑、设备状态监控等,传统的 HTTP 请求轮询方式不仅浪费带宽,还无法做到服务端主动推送。WebSocket 协议在完成一次 HTTP 握手之后,会在客户端和服务端之间建立一条持久化的全双工通道,双方都可以随时发送数据,通信开销远低于轮询。本文将以 Spring Boot 为例,完整演示如何搭建一个可用的 WS 服务,并覆盖配置、编码、前端对接以及部署阶段的常见问题。

Spring Boot 整合 WebSocket 实现 WS 通信的完整步骤详解

一、引入依赖并编写 WebSocket 配置

第一步是在 pom.xml 中加入 spring-boot-starter-websocket 依赖。这个 starter 内置了 Tomcat 对 WebSocket 的支持以及 Spring 封装好的相关类,不需要额外引入其他包。依赖坐标如下:

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

接下来需要向容器注册一个 ServerEndpointExporter。这个 Bean 的作用是扫描项目中所有带 @ServerEndpoint 注解的类,并把它们注册到 WebSocket 容器中。很多人写完服务端代码却发现连接始终报 404,十有八九就是漏掉了这个配置。注意,如果应用最终打成 war 包部署到外置 Tomcat 中,这个 Bean 不需要注册,由容器自己管理,重复注册反而会报错。

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

另外还要留意 URL 路径前缀问题。如果 application.yml 中配置了 server.servlet.context-path,WebSocket 的连接地址也需要带上这个前缀,否则前端会一直连不上,这是实际开发中非常高频的一个坑。

二、编写服务端端点与前端对接

Spring Boot 中实现 WS 服务端有两种主流方式:一是直接使用 JSR-356 规范的 @ServerEndpoint 注解,二是使用 Spring 自带的 WebSocketHandler。前者写法简洁,接近原生 Java WebSocket,适合简单场景;后者可以配合拦截器、更好的生命周期控制,适合复杂业务。先看 @ServerEndpoint 方式的完整代码:

@Component
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {

    // 线程安全容器,保存每个客户端对应的会话
    private static final ConcurrentHashMap<String, Session> SESSION_MAP = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        SESSION_MAP.put(userId, session);
        System.out.println("用户 " + userId + " 已连接,当前在线数:" + SESSION_MAP.size());
    }

    @OnMessage
    public void onMessage(String message, @PathParam("userId") String userId) {
        System.out.println("收到来自 " + userId + " 的消息:" + message);
        // 收到消息后回显,也可以转发给其他用户
        sendToUser(userId, "服务端已收到:" + message);
    }

    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        SESSION_MAP.remove(userId);
        System.out.println("用户 " + userId + " 已断开");
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 单发消息
    public void sendToUser(String userId, String message) {
        Session session = SESSION_MAP.get(userId);
        if (session != null && session.isOpen()) {
            // 异步发送,避免阻塞
            session.getAsyncRemote().sendText(message);
        }
    }

    // 广播消息
    public void broadcast(String message) {
        SESSION_MAP.values().forEach(s -> {
            if (s.isOpen()) {
                s.getAsyncRemote().sendText(message);
            }
        });
    }
}

这里有几个容易被忽略的细节。第一,@ServerEndpoint 标注的类默认不是单例的,每来一个连接容器都会创建一个新实例,所以成员变量不能跨连接共享状态,必须用 static 的 Map 保存会话,而且要选线程安全的容器。第二,发送消息优先使用 getAsyncRemote() 异步接口,getBasicRemote() 是同步阻塞的,在广播场景下容易拖慢整体吞吐。第三,@OnError 触发后通常会紧跟 @OnClose,在错误处理里记得做幂等清理,避免重复移除。

前端对接非常简单,浏览器原生支持 WebSocket 对象,几行代码就能连上:

const userId = '1001';
const socket = new WebSocket('ws://localhost:8080/ws/' + userId);

socket.onopen = function () {
    console.log('连接建立成功');
    socket.send('hello server');
};

socket.onmessage = function (event) {
    console.log('收到服务端消息:' + event.data);
};

socket.onclose = function () {
    console.log('连接已关闭');
};

// 发消息直接调用
function sendMsg(text) {
    if (socket.readyState === WebSocket.OPEN) {
        socket.send(text);
    }
}

注意 ws 协议和 wss 的区别:生产环境走 HTTPS 时,前端必须把地址写成 wss:// 开头,否则浏览器会因为混合内容策略直接拦截连接。

三、常见问题排查与进阶方案

实际上线后会遇到不少环境层面的问题。第一个是 Nginx 反向代理。默认情况下 Nginx 不会转发 Upgrade 头,WS 握手会失败,需要单独配置 proxy_set_header Upgrade 和 Connection,并适当调大 read timeout,否则连接会在默认 60 秒后被断开:

location /ws/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 300s;
}

第二个是心跳保活。浏览器和移动网络中,空闲的 WS 连接很容易被中间设备掐掉,建议客户端每 30 秒发一次 ping 文本,服务端收到后回 pong;如果连续几个周期没收到心跳,就主动清理 Session 释放资源。配合前端的重连机制(断开后指数退避重连),可以做到用户无感知的连接恢复。

第三个是水平扩展问题。应用部署多个实例时,Session 只存在于当前 JVM 中,用户 A 连在节点一,用户 B 连在节点二,A 想发消息给 B 就找不到会话了。常见解法有两种:一是引入 MQ 或 Redis 发布订阅做消息中转,由持有目标连接的节点执行真正下发;二是用网关按用户 ID 做一致性哈希,保证同一用户始终落在同一节点。前者实现成本低、通用性好,推荐优先考虑。

如果业务需要消息路由、订阅发布这类语义更丰富的模型,可以直接上 STOMP 子协议。Spring 对 STOMP 的支持非常完善,通过 @EnableWebSocketMessageBroker 开启后就能用 @MessageMapping 和 @SendTo 编写类似 MVC 的消息处理代码,客户端用 SockJS 兜底还能兼容不支持 WS 的老旧浏览器。对于简单的推送场景,本文介绍的裸 WebSocket 方案已经完全够用;对于复杂的 IM 系统,STOMP 加消息代理才是更合适的架构。

Spring BootWebSocketWS通信修改时间:2026-09-08 23:23:22

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/53042.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。