STOMP(Simple Text Oriented Messaging Protocol)是一个建立在WebSocket之上的简单文本消息协议,它为原始的WebSocket连接增加了发布订阅语义。原始的WebSocket只提供了一条裸连接,服务端和客户端虽然可以互发数据,但消息该发给谁、发到哪个目的地,都需要开发者自己设计协议。而STOMP通过SEND、SUBSCRIBE、MESSAGE等固定帧格式,让消息具备路由能力,配合Spring内置的消息代理,实现广播和点对点推送变得非常简单。下面详细介绍Spring Boot整合STOMP的完整过程。

一、为什么选择STOMP而不是裸WebSocket
裸WebSocket的通信模型很简单:一个连接对应一个会话,服务端要给某个用户发消息,就必须自己维护Session与用户的映射关系,一旦连接数量增大,管理成本会明显上升。同时浏览器端的代码也需要自己解析消息格式,判断这条消息属于哪种业务类型,扩展性比较差。
STOMP在WebSocket之上抽象出三类核心角色:客户端可以SUBSCRIBE订阅某个目的地,可以SEND向某个目的地发送消息;消息代理负责根据目的地把消息分发给所有订阅者。这种模型天然适合聊天室、站内通知、监控大屏推送等场景。比如一个股票行情系统,客户端只需订阅/topic/stock/AAPL,服务端往这个目的地推消息,所有订阅者都能收到,完全不需要手动管理连接列表。
Spring对STOMP的支持非常完善,spring-websocket和spring-messaging两个模块提供了完整的注解驱动编程模型。开发者可以用@MessageMapping处理客户端发来的消息,用SimpMessagingTemplate主动推送消息,开发体验和写普通Controller几乎没有区别。
二、服务端整合与核心配置
首先在pom.xml中引入相关依赖,Spring Boot已经把WebSocket相关内容打成了一个starter,直接引入即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>接下来编写WebSocket配置类,这是整个整合的核心。配置类需要继承WebSocketMessageBrokerConfigurer,重点实现两个方法:registerStompEndpoints注册STOMP端点,configureMessageBroker配置消息代理规则。
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS(); // 开启SockJS降级支持
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 客户端订阅前缀,消息代理会处理以/topic和/queue开头的订阅
registry.enableSimpleBroker("/topic", "/queue");
// 客户端发送消息的前缀,发往前缀下的目的地会进入@MessageMapping方法
registry.setApplicationDestinationPrefixes("/app");
// 点对点推送时使用的用户前缀
registry.setUserDestinationPrefix("/user");
}
}上面的配置里有几个关键点需要理解。/ws是WebSocket握手端点,客户端连接的就是这个地址。enableSimpleBroker启用的是内置的内存消息代理,适合单机应用;如果需要集群部署,可以改用enableStompBrokerRelay连接外部的RabbitMQ或ActiveMQ,这样多台服务节点可以共享消息。setApplicationDestinationPrefixes指定的/app前缀很关键,客户端发送到/app/hello的消息会被路由到@MessageMapping("/hello")标注的方法,而不是直接进入消息代理。
三、服务端接收消息与主动推送
服务端处理客户端消息使用@MessageMapping注解,功能类似MVC中的@RequestMapping。方法的返回值可以通过@SendTo指定推送到某个目的地,也可以注入SimpMessagingTemplate获得更灵活的推送能力。
@Controller
public class MessageController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
// 处理客户端发到 /app/hello 的消息,并广播到 /topic/greetings
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // 模拟处理耗时
return new Greeting("Hello, " + message.getName() + "!");
}
// 主动推送,适合在业务Service中调用
public void pushToUser(String userId, String payload) {
// 点对点:只有指定用户能收到
messagingTemplate.convertAndSendToUser(userId, "/queue/notify", payload);
}
public void pushBroadcast(String payload) {
// 广播:所有订阅了/topic/notice的客户端都能收到
messagingTemplate.convertAndSend("/topic/notice", payload);
}
}广播推送和点对点推送的实现方式有明显差异。广播直接调用convertAndSend,目的地以/topic开头,所有订阅者共享同一条消息。点对点推送调用convertAndSendToUser,框架会自动在目的地前面拼上用户前缀,实际发往类似/user/{userId}/queue/notify的地址,而客户端只需要订阅/user/queue/notify即可,Spring会自动完成用户与订阅的匹配。如果同一个用户开了多个浏览器标签页,每个连接都会收到消息,这符合大多数业务场景的预期。
四、前端连接与订阅消息
前端推荐使用sockjs-client和stompjs这两个库。SockJS提供了浏览器兼容降级,在WebSocket不可用时会自动切换到xhr-streaming等替代方案。客户端代码的基本流程是:建立连接、订阅目的地、发送消息。
var socket = new SockJS('http://127.0.0.1:8080/ws');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
// 订阅广播消息
stompClient.subscribe('/topic/notice', function (message) {
console.log('收到广播: ' + message.body);
});
// 订阅个人消息,框架自动处理用户前缀
stompClient.subscribe('/user/queue/notify', function (message) {
console.log('收到私信: ' + message.body);
});
});
// 向服务端发送消息,会被@MessageMapping("/hello")处理
function sendHello() {
stompClient.send("/app/hello", {}, JSON.stringify({name: '张三'}));
}需要注意的是,点对点推送要求服务端知道当前连接属于哪个用户。最简单的做法是在握手阶段通过URL参数传递用户标识,并配合HandshakeInterceptor把用户信息放入WebSocket会话,再自定义Principal解析逻辑。如果系统已经集成了Spring Security,基于HTTP会话的认证可以自然延伸到WebSocket连接上,convertAndSendToUser就能直接使用登录用户的身份。
五、常见问题与生产实践建议
跨域问题是整合时最常踩的坑。浏览器发起WebSocket握手时同样受同源策略限制,如果前后端不同源,必须调用setAllowedOriginPatterns("*")或指定具体域名,否则握手会直接失败。生产环境建议写明白名单域名,不要长期保留星号。
心跳配置也值得关注。STOMP协议自带心跳机制,可以在configureWebSocketTransport中设置服务端心跳间隔。没有心跳的情况下,如果客户端网络异常断开,服务端可能长时间感知不到,造成僵尸连接和消息推送失败。配置心跳后,连续几个周期没有收到对方心跳就会被判定为断连,触发资源清理。
认证与安全方面,建议通过ChannelInterceptor拦截CONNECT帧,从STOMP头部中取出token进行校验,校验通过后再放行后续订阅。同时要限制订阅权限,比如普通用户不允许订阅管理员专属的/topic/admin,可以在subscribe阶段做拦截校验,防止越权获取消息。集群部署时记得把内置SimpleBroker换成外部消息代理,否则消息只会在单节点内分发,不同节点上的用户互相收不到消息。只要把这些细节处理好,STOMP方案完全可以支撑中等规模系统的实时消息需求。
Spring Boot STOMPWebSocket消息推送SpringMessaging修改时间:2026-09-10 21:44:57