WebSocket 与 STOMP 基础(spring-websocket)

HTTP 一问一答,适合请求响应;聊天、通知、实时看板需要服务端主动推,WebSocket 提供全双工长连接。spring-websocket 在 Spring MVC 应用里集成 WebSocket,STOMP 则给它加上「话题订阅」的消息语义。

依赖与容器要求

需要 spring-websocket(原生握手)与 spring-messaging(STOMP)两个模块,版本与 spring-webmvc 保持一致;容器需支持 WebSocket(如 Tomcat 9.0+、Jetty 9.4+),具体对应关系以官方文档为准。

方式一:原生 WebSocketHandler

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatHandler(), "/ws/chat")
                .setAllowedOriginPatterns("*"); // 跨域放行;生产按源收紧
    }
}

处理器继承 TextWebSocketHandler,四个生命周期回调:

public class ChatHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        System.out.println("连接建立:" + session.getId());
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 收到客户端文本:echo 回去
        session.sendMessage(new TextMessage("echo: " + message.getPayload()));
    }
}

方式二:STOMP——给消息加「频道」

STOMP 是基于帧的消息协议,把连接抽象成:客户端向某个「目的地」发送(send),或订阅某个目的地(subscribe)接收推送。Spring 用 @EnableWebSocketMessageBroker 集成:

@Configuration
@EnableWebSocketMessageBroker
public class StompConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")            // 握手端点
                .setAllowedOriginPatterns("*")
                .withSockJS();                 // 浏览器降级方案(SockJS)
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic", "/queue"); // 服务端广播/点对点前缀
        registry.setApplicationDestinationPrefixes("/app"); // 客户端发给服务端的前缀
    }
}

服务端接收与推送

@Controller
public class ChatController {
    @MessageMapping("/chat")                     // 客户端发往 /app/chat
    @SendTo("/topic/messages")                   // 广播给订阅 /topic/messages 的所有人
    public ChatMessage chat(ChatMessage msg) {
        return new ChatMessage(msg.getFrom(), msg.getText());
    }
}

需要主动推送(非应答式)时注入 SimpMessagingTemplate:

@Service
public class NotifyService {
    private final SimpMessagingTemplate template;

    public NotifyService(SimpMessagingTemplate template) {
        this.template = template;   // 由框架自动注入
    }
    public void notifyAll(String text) {
        template.convertAndSend("/topic/notices", new Notice(text)); // 广播
    }
}

客户端最小示例(JS)

var socket = new SockJS('https://example.com/ws'); // 换成真实地址
var stomp = Stomp.over(socket);
stomp.connect({}, function () {
    stomp.subscribe('/topic/messages', function (frame) { /* 收消息 */ });
    stomp.send('/app/chat', {}, JSON.stringify({ from: '张三', text: '你好' }));
});

进阶方向

  • 点对点:@SendToUser 配合队列前缀,实现「只推给某个人」。
  • 完整消息代理:enableSimpleBroker 适合小规模;大规模生产可接 RabbitMQ/ActiveMQ 的外部 STOMP Broker(enableStompBrokerRelay)。

WebSocket 解决「连接」,STOMP 解决「频道与订阅」。小项目用简单 Broker + @MessageMapping/@SendTo 就够;要广播通知、聊天室、实时看板,先把「端点、前缀、目的地」三个概念理清,骨架搭好后都是往 /topic 或 /queue 上发消息的事。

笔记加载中…