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-client 和 stompjs 库进行连接。

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 服务。

相关推荐
云浪2 小时前
Go 堆实战:从完全二叉树到 Top-K 与任务调度
后端·go
光依旧9 小时前
MCP实战手记系列(十):MCP Server高可用实战
spring boot·高可用·nginx负载均衡·redis哨兵·mcp·微服务容错·resilience4j熔断
刚子编程12 小时前
我用 ASP.NET Core 做了个水稻病虫害检查系统
后端·asp.net
谢亮_vipxieliang12 小时前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
省钱兄--zs13 小时前
西安24小时自助健身房解决方案实战:系统开发与部署全流程指南
java·spring boot·系统架构·intellij-idea·需求分析
Knight_AL14 小时前
基于 Netty 实现的 WebSocket 服务端
网络·websocket·网络协议
码事漫谈15 小时前
三步改掉 AI 味,附可直接复制的去 AI 味提示词
后端
IT_陈寒15 小时前
Redis卡顿的锅,这次真不是大key的错
前端·人工智能·后端
可乐鸡翅yeah_15 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放