首页 / Spring Boot 入门教程 / WebSocket 实时通信

Spring Boot 入门教程

WebSocket 实时通信

本教程共 48 篇 · 第 25 篇 · 更新于 2026-08-13 · 约 3 分钟阅读

Spring BootWebSocketSTOMP实时通信消息推送SockJS

本节目标:理解 WebSocket 相比 HTTP 轮询的优势,会用 STOMP 搭建服务端推送,让浏览器实时收到消息。

为什么需要 WebSocket

HTTP 是「一问一答」的协议。客户端发请求,服务端给响应,连接就结束了。服务端想主动通知客户端,做不到。

早期解决方案是轮询:前端定时发请求问「有消息了吗」。

轮询的问题一眼可见:

  • 大部分请求没有新数据,浪费带宽。
  • 实时性取决于轮询间隔,间隔短就费资源。
  • 服务端连接反复建立销毁,压力大。

长轮询改良了一点:服务端没数据就挂起连接,有数据再响应。但连接悬挂着,每个客户端占一个线程,并发一大就撑不住。

WebSocket 换了个思路:建立一条全双工的 TCP 连接,客户端和服务端随时互发消息,不再一问一答。

方式实时性资源消耗适用
短轮询低频简单场景
长轮询老系统兼容
WebSocket聊天、通知、看板

WebSocket 怎么建立连接

WebSocket 不走新协议,握手还是 HTTP。

浏览器发一个带 Upgrade 头的请求,服务端同意后,连接升级为 WebSocket,此后双方在一条连接上自由收发。

Spring Boot 对 WebSocket 支持两层:

  • 底层 WebSocketHandler:处理原始文本/二进制消息。
  • 上层 STOMP:在 WebSocket 之上定义消息格式和路由,推荐用这个。

引入依赖

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

前端要连 SockJS 和 STOMP 的客户端库,用 WebJars 引:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>sockjs-client</artifactId>
    <version>1.5.1</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>stomp-websocket</artifactId>
    <version>2.3.4</version>
</dependency>

配置消息代理

STOMP 把消息分成两类目的地:/topic 广播给所有订阅者,/app 发给服务端处理。

配置类:

package com.example.demo.config;

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 WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 广播目的地前缀
        registry.enableSimpleBroker("/topic");
        // 客户端发消息的前缀
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 握手端点,SockJS 兜底
        registry.addEndpoint("/ws").withSockJS();
    }
}
Note

旧教程常写继承 AbstractWebSocketMessageBrokerConfigurer,那个类已废弃。Spring Boot 3.x/4.x 直接实现 WebSocketMessageBrokerConfigurer 接口即可。

消息处理器:接收与广播

@MessageMapping 接收 /app 前缀下的消息,@SendTo 把结果广播到指定主题:

package com.example.demo.controller;

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

@Controller
public class GreetingController {

    // 客户端发到 /app/hello
    @MessageMapping("/hello")
    // 结果广播到 /topic/greetings
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) {
        return new Greeting("你好," + message.getName() + "!");
    }
}

两个消息模型:

package com.example.demo.model;

public class HelloMessage {

    private String name;

    public HelloMessage() {
    }

    public HelloMessage(String name) {
        this.name = name;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
package com.example.demo.model;

public class Greeting {

    private String content;

    public Greeting() {
    }

    public Greeting(String content) {
        this.content = content;
    }

    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
}

服务端主动推送也简单:注入 SimpMessagingTemplate,任何地方都能往主题发消息:

package com.example.demo.service;

import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
public class NotifyService {

    private final SimpMessagingTemplate messagingTemplate;

    public NotifyService(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    // 业务代码里调用,全端广播
    public void notifyAll(String content) {
        messagingTemplate.convertAndSend("/topic/notices", content);
    }
}

这解决了轮询的痛点:有新数据才推送,连接只有一条。

前端订阅示例

页面 src/main/resources/static/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 示例</title>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
</head>
<body>
    <input id="name" placeholder="输入名字" />
    <button onclick="send()">发送</button>
    <ul id="messages"></ul>

    <script>
        var stompClient = null;

        function connect() {
            // 与后端 /ws 端点握手
            var socket = new SockJS('/ws');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function () {
                // 订阅广播主题
                stompClient.subscribe('/topic/greetings', function (msg) {
                    var li = document.createElement('li');
                    li.textContent = JSON.parse(msg.body).content;
                    document.getElementById('messages').appendChild(li);
                });
            });
        }

        function send() {
            var name = document.getElementById('name').value;
            // 发到 /app/hello
            stompClient.send('/app/hello', {}, JSON.stringify({name: name}));
        }

        connect();
    </script>
</body>
</html>

页面加载即连接,发送后所有打开页面的用户都能收到广播。

Warning

SockJS 的 withSockJS() 会在浏览器不支持原生 WebSocket 时降级走 HTTP 长轮询,兼容老浏览器。纯内网新项目可以去掉 SockJS,直连原生 WebSocket。

原始 WebSocketHandler

不想用 STOMP,也可以直接处理原始消息。实现 TextWebSocketHandler

package com.example.demo.websocket;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class EchoHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message)
            throws Exception {
        // 收到什么回什么
        session.sendMessage(new TextMessage("收到:" + message.getPayload()));
    }
}

WebSocketConfigurer 注册:

package com.example.demo.config;

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;

import com.example.demo.websocket.EchoHandler;

@Configuration
@EnableWebSocket
public class RawWebSocketConfig implements WebSocketConfigurer {

    private final EchoHandler echoHandler;

    public RawWebSocketConfig(EchoHandler echoHandler) {
        this.echoHandler = echoHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoHandler, "/echo");
    }
}

适合简单回声、小流量场景。消息要路由、要群发,还是 STOMP 省心。

本节小结

  • 轮询费资源、实时性差,WebSocket 全双工连接是实时场景的正解。
  • STOMP 基于 WebSocket 定义消息路由,Spring Boot 推荐用它。
  • @MessageMapping 收消息,@SendTo 广播,SimpMessagingTemplate 主动推。
  • 配置类实现 WebSocketMessageBrokerConfigurer,别再用废弃的抽象类。