跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

Spring Boot 集成 WebSocket 实战:从原生到 STOMP 全解析

Spring Boot WebSocket 集成方案涵盖原生接口与 STOMP 协议两种主流实现方式。内容涉及基础概念、配置步骤、安全鉴权、集群部署及性能优化等实战要点。通过原生 WebSocket 与 STOMP over WebSocket 的代码对比,展示了如何构建实时通知系统,解决传统 HTTP 轮询带来的延迟与资源浪费问题,适用于即时通讯、股票行情等实时性要求较高的应用场景。

落日余晖发布于 2026/4/10更新于 2026/7/1845 浏览

背景与动机

随着互联网应用对实时性要求的提升,传统的 HTTP 请求 - 响应模式在处理股票行情、即时消息等场景时显得力不从心。频繁轮询浪费资源,服务器也无法主动通知客户端。WebSocket 协议的出现完美解决了这一难题,它允许服务器主动向客户端推送数据,实现真正的双向通信。

Spring Boot 作为主流的 Java 微服务框架,对 WebSocket 提供了良好的支持。本文将深入浅出地讲解如何在 Spring Boot 中集成 WebSocket,涵盖原生 WebSocket、STOMP 协议、安全集成及集群部署等实战内容,帮助读者全面掌握这一技术。

WebSocket 基础

什么是 WebSocket?

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,由 IETF 定为标准 RFC 6455。它使得客户端和服务器之间的数据交换更加简单,服务端可以主动推送数据。

与 HTTP 的关系

WebSocket 建立连接时使用 HTTP 协议的 Upgrade 机制进行升级。客户端发起带有特殊头部的 HTTP 请求,服务器返回 101 状态码后,连接便切换到 WebSocket 协议。两者都基于 TCP,但 HTTP 是半双工且需频繁建立连接,而 WebSocket 连接一旦建立即可持续通信,开销更小。

工作流程

  1. 握手阶段:客户端发起 HTTP 请求,携带 Upgrade: websocket 头。
  2. 协议切换:服务器返回 101 状态码,同意切换。
  3. 数据传输:双方互相发送文本或二进制数据帧。
  4. 关闭连接:任意一方发送关闭帧,另一方响应后关闭 TCP。

Spring Boot 集成准备

Spring 框架从 4.0 开始引入 WebSocket 模块,Spring Boot 通过自动配置简化了过程。主要支持两种方式:

  • 原生 WebSocket:基于 JSR-356,使用 @ServerEndpoint 注解。
  • STOMP over WebSocket:在 WebSocket 之上使用 STOMP 协议,提供类似消息队列的订阅发布模型。

创建一个基本 Spring Boot 项目,选择以下依赖:

<dependencies>
    <!-- Spring Boot WebSocket Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web
    

</artifactId>
</dependency>
</dependencies>

原生 WebSocket 实现

我们先从最基础的原生 WebSocket 开始,了解握手、消息收发等基本流程。

配置处理器

在 Spring Boot 中使用原生 WebSocket,通常通过注册 ServerEndpointExporter Bean 来自动扫描并注册带有 @ServerEndpoint 注解的类。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

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

注意:如果你使用内嵌的 Servlet 容器(如 Tomcat),ServerEndpointExporter 会自动注册端点。如果部署到外部容器,可能需要额外配置,但 Spring Boot 内嵌容器通常够用。

编写处理类

使用 @ServerEndpoint 标记类并定义路径,通过 @OnOpen、@OnMessage 等注解处理事件。下面是一个简单的回声示例,同时支持主动推送:

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/ws/echo")
public class EchoWebSocket {
    private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session) {
        sessions.add(session);
        System.out.println("新连接加入,当前连接数:" + sessions.size());
        try {
            session.getBasicRemote().sendText("连接成功,欢迎!");
        } catch (IOException e) { e.printStackTrace(); }
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("收到消息:" + message);
        try {
            session.getBasicRemote().sendText("Echo: " + message);
        } catch (IOException e) { e.printStackTrace(); }
        broadcast("用户说:" + message);
    }

    @OnClose
    public void onClose(Session session) {
        sessions.remove(session);
        System.out.println("连接关闭,当前连接数:" + sessions.size());
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    public static void broadcast(String message) {
        for (Session session : sessions) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) { e.printStackTrace(); }
        }
    }
}

前端测试

前端使用浏览器原生的 WebSocket API 连接后端:

<!DOCTYPE html>
<html>
<head><title>WebSocket Echo Test</title></head>
<body>
<input type="text" id="messageInput" placeholder="输入消息" />
<button onclick="sendMessage()">发送</button>
<div id="messages"></div>
<script>
var ws = new WebSocket("ws://localhost:8080/ws/echo");
ws.onopen = function() { appendMessage("连接已建立"); };
ws.onmessage = function(event) { appendMessage("收到:" + event.data); };
ws.onclose = function() { appendMessage("连接关闭"); };
function sendMessage() {
    var msg = document.getElementById("messageInput").value;
    ws.send(msg);
    appendMessage("发送:" + msg);
    document.getElementById("messageInput").value = '';
}
function appendMessage(msg) {
    var div = document.getElementById("messages");
    div.innerHTML += "<p>" + msg + "</p>";
}
</script>
</body>
</html>

启动应用访问 HTML 页面即可测试。若需后台主动推送,可在任何地方调用 broadcast() 方法,例如结合定时任务每秒推送时间。

基于 STOMP 的实现

STOMP(Simple Text Oriented Messaging Protocol)是更高级的消息协议,Spring 提供了对 STOMP over WebSocket 的支持,使得我们可以像使用消息队列一样处理消息。

配置消息代理

需要创建配置类实现 WebSocketMessageBrokerConfigurer 接口。

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
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp")
                .setAllowedOrigins("*")
                .withSockJS(); // 启用 SockJS 降级方案
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic", "/queue");
        registry.setUserDestinationPrefix("/user");
    }
}
  • registerStompEndpoints:注册 STOMP 端点,客户端通过该端点连接。
  • configureMessageBroker:配置消息代理,设置应用目的地前缀、代理前缀及用户目的地前缀。

Controller 处理消息

使用 @MessageMapping 注解处理客户端发送到特定目的地的消息。

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000);
        return new Greeting("Hello, " + message.getName() + "!");
    }
}
  • @MessageMapping("/hello"):当客户端发送到 /app/hello 的消息会路由到此方法。
  • @SendTo("/topic/greetings"):返回值将发送到 /topic/greetings 目的地,所有订阅该主题的客户端都会收到。

灵活推送消息

SimpMessagingTemplate 是 Spring 提供的推送工具类,可注入到任何 Bean 中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PushController {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @GetMapping("/push")
    public String pushToAll() {
        messagingTemplate.convertAndSend("/topic/news", "突发新闻:Spring Boot 3.0 发布!");
        return "推送成功";
    }

    @GetMapping("/push/user")
    public String pushToUser(String userId) {
        messagingTemplate.convertAndSendToUser(userId, "/message", "您有一条私信");
        return "私信推送成功";
    }
}

convertAndSend 用于广播,convertAndSendToUser 用于点对点。注意 convertAndSendToUser 默认会拼接成 /user/{userId}/message 这样的目的地。

前端接入

前端需引入 SockJS 和 STOMP.js 库。

<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>
var stompClient = null;
function connect() {
    var socket = new SockJS('/ws-stomp');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function(frame) {
        console.log('连接成功:' + frame);
        stompClient.subscribe('/topic/greetings', function(greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
        stompClient.subscribe('/user/message', function(message) {
            showGreeting("私信:" + message.body);
        });
    }, function(error) {
        console.log('连接失败:' + error);
    });
}
function sendName() {
    var name = document.getElementById('name').value;
    stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
}
window.onload = connect;
</script>

安全集成

在实际应用中,WebSocket 连接往往需要鉴权。Spring Security 提供了与 WebSocket 的集成。

配置 Security

添加 Spring Security 依赖,并配置允许 WebSocket 端点需要认证。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/ws-stomp/**").authenticated()
            .anyRequest().permitAll()
            .and().formLogin().permitAll()
            .and().logout().permitAll()
            .and().csrf().disable();
        return http.build();
    }
}

获取用户信息

为了让 WebSocket 知道当前用户是谁,可以在握手阶段传递认证信息。如果已经通过 HTTP 登录(如使用表单登录),由于同源,JSESSIONID Cookie 会自动携带,Spring Security 会识别出已登录的用户。对于 Token 认证,需要在 ChannelInterceptor 中解析 Token 并设置到 SecurityContext 中。

集群环境下的 WebSocket

生产环境中,应用通常部署多个实例。WebSocket 会话保存在节点内存中,其他节点无法访问,导致跨节点推送失败。

解决方案

  • 使用外部消息代理:如 RabbitMQ、Kafka。所有节点连接到同一个代理,实现跨节点广播。
  • 会话集中存储:将会话信息存储在 Redis 中,推送时查找对应节点转发。

推荐使用 RabbitMQ 作为外部消息代理。修改配置使用 StompBrokerRelay 代替简单代理。

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.setApplicationDestinationPrefixes("/app");
    registry.enableStompBrokerRelay("/topic", "/queue")
            .setRelayHost("localhost")
            .setRelayPort(61613)
            .setClientLogin("guest")
            .setClientPasscode("guest");
    registry.setUserDestinationPrefix("/user");
}

性能优化与最佳实践

  • 心跳机制:配置心跳间隔保持连接活跃。
  • 线程模型:自定义线程池参数,防止高并发下线程耗尽。
  • 消息大小限制:调整 messageSizeLimit 防止大消息阻塞。
  • 连接数限制:使用 Nginx 负载均衡,调整 Tomcat 最大连接数。

常见问题

  • 连接失败(404):检查端点路径及上下文路径。
  • 消息丢失:确认客户端订阅了正确的目的地。
  • 断线重连:前端监听断开事件并延时重连。
  • 跨域问题:配置 setAllowedOrigins,生产环境指定具体域名。

总结

本文详细介绍了在 Spring Boot 中集成 WebSocket 的两种方式:原生 WebSocket 和基于 STOMP 的消息代理方式。探讨了消息的广播与点对点推送、安全集成、集群部署以及性能优化。WebSocket 技术为实时 Web 应用提供了强大的支持,结合 Spring Boot 的便捷性,开发者可以快速构建出高实时性的应用。

目录

  1. 背景与动机
  2. WebSocket 基础
  3. 什么是 WebSocket?
  4. 与 HTTP 的关系
  5. 工作流程
  6. Spring Boot 集成准备
  7. 原生 WebSocket 实现
  8. 配置处理器
  9. 编写处理类
  10. 前端测试
  11. 基于 STOMP 的实现
  12. 配置消息代理
  13. Controller 处理消息
  14. 灵活推送消息
  15. 前端接入
  16. 安全集成
  17. 配置 Security
  18. 获取用户信息
  19. 集群环境下的 WebSocket
  20. 解决方案
  21. 性能优化与最佳实践
  22. 常见问题
  23. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • OpenClaw 开源桌面 AI Agent 框架功能与架构解析
  • OpenClaw 部署指南:安装、Coding Plan 配置与飞书机器人接入
  • 网络安全就业前景与核心岗位详解
  • 2025 年 AI 大模型与编程工具发展综述
大语言模型(LLM)快速理解指南
  • AI 前端技术解析:从概念到实战进阶路线
  • Ubuntu 22.04 下 VMware Tools 文件复制粘贴与拖拽问题解决
  • 基于 SpringBoot 和 Streamable-HTTP 构建 MCP Server
  • 机器人表情模拟实现:Arduino 控制面部舵机项目详解
  • 语义化 AI 驱动器:提示词工程的技术演进与未来图景
  • Stable Diffusion WebUI 整合包安装与使用指南
  • STL 转 STEP 格式转换工具 stltostp 使用指南
  • Git for Windows 安装与配置详解
  • Formality 原语(primitive)概念解析
  • David Beazley 开源:基于实战的 Python 极速入门指南
  • 通义万相 2.1 文生视频技术解析与部署实践
  • 策略模式实战:通过组合实现算法的灵活解耦
  • 基于 Rokid 眼镜的 AI 天气应用、GPS 定位与旅游规划实现
  • Spring Web 模块核心解析与 RESTful API 实战
  • AI提示词:零基础入门与核心概念
  • 相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online