Spring Boot 3.x WebSocket 实战教程

一、什么是 WebSocket?为什么需要它?

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 允许服务器主动向客户端推送数据,非常适合实时性要求高的场景。

特性 HTTP WebSocket
通信模式 请求-响应(被动) 全双工(双向主动)
连接状态 无状态,短连接 有状态,持久连接
实时性 低(需轮询,延迟高) 高(毫秒级推送)
头部开销 大(每次携带完整头信息) 小(握手后仅需少量字节)

典型应用场景:即时聊天室、股票行情推送、在线协作编辑、游戏实时状态同步。

二、环境准备与依赖

本教程基于 Spring Boot 3.x (对应 Spring Framework 6.x)和 JDK 17+

1、创建项目

使用 Spring Initializr 创建项目,勾选以下依赖:

  • Spring Web
  • Spring WebSocket
  • Lombok(可选,用于简化代码)

2、Maven 依赖

确保 pom.xml 中包含以下核心依赖:

复制代码
<dependencies>
    <!-- Web基础依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- WebSocket核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
</dependencies>
三、核心实现:基于 STOMP 的消息代理模式

Spring 提供了基于 STOMP 协议的 WebSocket 支持,它比原生 API 更易于使用,类似于消息队列的发布/订阅模式。

1、配置 WebSocket

创建一个配置类,启用 WebSocket 消息代理并注册端点。

复制代码
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 // 启用WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 启用简单的内存消息代理,客户端订阅以 "/topic" 开头的目的地
        registry.enableSimpleBroker("/topic");
        // 设置客户端发送消息的前缀,即 "/app"
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册端点 "/ws",客户端将通过此路径建立连接
        // withSockJS() 提供了对不支持 WebSocket 浏览器的降级支持
        registry.addEndpoint("/ws").withSockJS();
    }
}

2、编写消息控制器

创建一个控制器来处理客户端的消息发送和广播。

复制代码
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    /**
     * 处理客户端发送的消息
     * @MessageMapping("/chat") 对应客户端发送目标 "/app/chat"
     * @SendTo("/topic/messages") 将返回值广播给所有订阅 "/topic/messages" 的客户端
     */
    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public ChatMessage sendChatMessage(ChatMessage message) {
        // 这里可以添加业务逻辑,如保存消息到数据库
        System.out.println("收到消息: " + message.getContent());
        return message;
    }
}

3、定义消息实体

复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
    private String username;
    private String content;
    private long timestamp;
}
四、前端客户端连接(HTML + JavaScript)

Spring WebSocket 默认支持 SockJS,前端可以使用 sockjs-clientstompjs 库进行连接。

1、引入依赖

在 HTML 文件中引入以下 CDN 资源:

复制代码
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

2、连接与通信代码

复制代码
<script type="text/javascript">
    var stompClient = null;

    // 1. 建立连接
    function connect() {
        // 创建 SockJS 对象,指向后端注册的端点 /ws
        var socket = new SockJS('/ws');
        // 使用 STOMP 协议包装
        stompClient = Stomp.over(socket);
        
        stompClient.connect({}, function (frame) {
            console.log('连接成功: ' + frame);
            // 2. 订阅消息主题
            stompClient.subscribe('/topic/messages', function (message) {
                // 接收服务器广播的消息
                showMessage(JSON.parse(message.body));
            });
        });
    }

    // 3. 发送消息
    function sendMessage() {
        var messageContent = document.getElementById('inputMessage').value;
        var chatMessage = {
            username: 'User_' + Math.floor(Math.random() * 1000),
            content: messageContent,
            timestamp: new Date().getTime()
        };
        // 发送到 /app/chat (对应后端的 @MessageMapping("/chat"))
        stompClient.send("/app/chat", {}, JSON.stringify(chatMessage));
    }

    function showMessage(message) {
        var response = document.getElementById('response');
        var p = document.createElement('p');
        p.innerText = message.username + ": " + message.content;
        response.appendChild(p);
    }
    
    // 页面加载完成后连接
    window.onload = connect;
</script>
五、进阶:使用原生 WebSocketHandler

如果你不需要 STOMP 协议,只想处理简单的文本消息,可以使用 WebSocketHandler

1、自定义 Handler

复制代码
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.util.concurrent.CopyOnWriteArrayList;

@Component
public class MyCustomHandler extends TextWebSocketHandler {

    // 线程安全的会话列表
    private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        session.sendMessage(new TextMessage("欢迎连接!当前在线人数: " + sessions.size()));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 广播消息给所有连接者
        for (WebSocketSession s : sessions) {
            if (s.isOpen()) {
                s.sendMessage(new TextMessage("广播: " + message.getPayload()));
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
    }
}

2、注册 Handler

在配置类中注入并注册:

复制代码
@Configuration
@EnableWebSocket // 注意:这里启用原生 WebSocket 支持
public class NativeWebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private MyCustomHandler myCustomHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册路径 /ws-native,允许跨域
        registry.addHandler(myCustomHandler, "/ws-native").setAllowedOrigins("*");
    }
}
六、常见问题排查
  • 404 Not Found :检查 @EnableWebSocketMessageBroker@EnableWebSocket 是否遗漏,以及端点路径 /ws 是否正确。

  • 跨域问题 :确保在 registerWebSocketHandlers 中配置了 .setAllowedOrigins("*") 或指定具体域名。

  • 握手失败:如果是生产环境使用 Nginx 反向代理,需要配置 Upgrade 头:

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

通过以上步骤,你可以在 Spring Boot 3.x 中快速搭建起稳定高效的 WebSocket 服务。

相关推荐
neoooo2 小时前
Spring AI MCP Server 开发指南
人工智能·后端·mcp
南璋2 小时前
MySQL排序踩坑:为什么"10"比"2"小?
后端
何陋轩2 小时前
Elasticsearch搜索引擎深度解析:把搜索核心讲透,面试都是小菜
后端·面试
Java编程爱好者2 小时前
JVM 详解:从内存结构到调优实战,Java 开发者必读
后端
小瓦码J码2 小时前
如何手动部署一个向量模型服务
人工智能·后端
Carsene2 小时前
Spring Boot 包扫描新姿势:AutoScan vs @Import vs @ComponentScan 深度对比
spring boot·后端
Gopher_HBo2 小时前
ReentrantReadWriteLock源码讲解
java·后端
文浩AI2 小时前
Claude Code 创始人 Boris Cherny 的并行工作流最佳实践
后端
武子康2 小时前
大数据-267 实时数仓-架构演进:Lambda与Kappa架构实战指南
大数据·后端