Spring Boot STOMP WebSocket消息已发至队列但订阅客户端未接收
Spring Boot + STOMP WebSocket 实时通知异常排查
问题背景
使用Spring Boot结合STOMP实现WebSocket实时通知功能,已配置WebSocket配置、安全配置及WebSocket处理器装饰器。
问题现象
- 可通过WebSocketKing成功连接到
ws://localhost:8080/notification/websocket端点,握手升级正常; - 不同客户端能成功订阅
/queue/unseen-bookings-1和/queue/unseen-bookings-2,日志确认订阅已处理; - 调用PATCH端点更新预订时,通过
messagingTemplate.convertAndSend()向对应队列发送通知,日志显示消息已广播,但两个客户端均未收到消息。
相关日志
订阅日志
2025-03-24 01:46:57 2025-03-24T01:46:57.940Z TRACE 1 --- [ XNIO-1 I/O-2] s.w.s.h.LoggingWebSocketHandlerDecorator : Handling TextMessage payload=[SUBSCRIBE_..], byteCount=54, last=true] in StandardWebSocketSession[id=0fe8173f-9c1b-6ce1-658e-38a33e556d1c, uri=/notification/websocket] 2025-03-24 01:46:57 2025-03-24T01:46:57.945Z TRACE 1 --- [ XNIO-1 I/O-2] o.s.messaging.simp.stomp.StompDecoder : Decoded SUBSCRIBE {id=["1"], destination=[/queue/unseen-bookings-1]} session=null 2025-03-24 01:46:57 2025-03-24T01:46:57.946Z TRACE 1 --- [ XNIO-1 I/O-2] o.s.w.s.m.StompSubProtocolHandler : From client: SUBSCRIBE /queue/unseen-bookings-1 id="1" session=0fe8173f-9c1b-6ce1-658e-38a33e556d1c 2025-03-24 01:46:57 2025-03-24T01:46:57.949Z DEBUG 1 --- [nboundChannel-2] o.s.m.s.b.SimpleBrokerMessageHandler : Processing SUBSCRIBE /queue/unseen-bookings-1 id="1" session=0fe8173f-9c1b-6ce1-658e-38a33e556d1c 2025-03-24 01:47:03 2025-03-24T01:47:03.493Z INFO 1 --- [MessageBroker-1] o.s.w.s.c.WebSocketMessageBrokerStats : WebSocketSession[1 current WS(1)-HttpStream(0)-HttpPoll(0), 1 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(0)-CONNECTED(0)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 3], outboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0], sockJsScheduler[pool size = 3, active threads = 1, queued tasks = 2, completed tasks = 0]
消息广播日志
Processing MESSAGE destination=/queue/unseen-bookings-1 session=null payload={"ownerUnseen":0,"renterUnseen":0} Broadcasting to 1 sessions. Processing MESSAGE destination=/queue/unseen-bookings-2 session=null payload={"ownerUnseen":0,"renterUnseen":1} Broadcasting to 1 sessions.
相关配置
已配置WebsocketSecurityConfiguration.java、WebsocketConfiguration.java、EmaWebSocketHandlerDecorator.java及PATCH端点代码。
疑问
- 为何日志显示消息已广播,但订阅客户端未接收?
- 日志中的
session=null是否相关? - 使用SimpleBroker时session是否重要?
- 是否存在阻止消息到达客户端的配置?
问题分析与解决方案
1. session=null的核心影响
日志里的session=null是关键问题:
- STOMP协议要求客户端必须先发送
CONNECT帧,待服务器返回CONNECTED帧后,才能建立有效会话上下文。但统计日志显示stompSubProtocol[processed CONNECT(0)-CONNECTED(0)-DISCONNECT(0)],说明客户端从未发送CONNECT帧,或服务器未正确处理该请求。 - 无有效会话的订阅属于“无状态”订阅,SimpleBroker无法将消息关联到具体的WebSocket会话,即便日志显示“Broadcasting to 1 sessions”,实际也无法完成投递。
2. 排查与修复方向
客户端侧
- 确认客户端在建立WebSocket连接后,是否正确发送STOMP的
CONNECT帧(若开启安全校验,需携带认证信息)。 - 确保客户端在收到
CONNECTED帧后,再发送SUBSCRIBE帧。部分WebSocket测试工具(如WebSocketKing)仅建立底层连接,不会自动发送STOMP的CONNECT请求,导致会话未初始化。
服务端配置
- WebSocket核心配置:检查
@EnableWebSocketMessageBroker的配置,确保simpleBroker()包含/queue前缀:@Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue", "/topic"); config.setApplicationDestinationPrefixes("/app"); } - 安全配置:若
WebSocketSecurityConfiguration拦截了STOMP端点,需放行CONNECT和SUBSCRIBE类型的请求(根据业务调整认证规则):@Override protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages.simpDestMatchers("/queue/**").authenticated() .simpTypeMatchers(CONNECT, SUBSCRIBE).permitAll(); } - 处理器装饰器:检查
EmaWebSocketHandlerDecorator是否拦截或修改了出站消息,确保handleMessage方法正确传递消息,未做无意义的拦截。
消息发送逻辑
- 确认
messagingTemplate.convertAndSend()的目标路径与客户端订阅路径完全一致,避免前缀错误(比如多了/app前缀),正确写法示例:messagingTemplate.convertAndSend("/queue/unseen-bookings-1", notificationPayload);
3. 验证步骤
- 使用支持STOMP的客户端(如Postman、STOMP.js)重新测试:先发送
CONNECT帧,确认收到CONNECTED后再订阅队列,最后触发PATCH接口。 - 开启
org.springframework.messaging的TRACE级日志,追踪消息从发送到出站的全链路,确认是否在某个环节被拦截。
内容的提问来源于stack exchange,提问作者Charfeddine Mohamed Ali
相关产品推荐
相关产品推荐

