1. 概述
本文将演示如何使用Spring WebSockets向特定会话或用户发送消息。关于基础概念可参考这篇入门文章。
2. WebSocket配置
首先需要配置消息代理和WebSocket应用接口:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting");
}
}
通过@EnableWebSocketMessageBroker
我们启用了基于STOMP协议的WebSocket消息代理。STOMP(流式文本定向消息协议)是WebSocket上的消息传递标准。注意该注解需配合@Configuration
使用。
虽然继承AbstractWebSocketMessageBrokerConfigurer
不是必须的,但在这个简单示例中能更方便地自定义配置。
关键配置点:
- ✅ 启用内存消息代理,处理
/topic
和/queue
前缀的消息 - ✅ 注册STOMP接口
/greeting
- ⚠️ 如需启用SockJS支持,需修改注册代码:
registry.addEndpoint("/greeting").withSockJS();
3. 通过拦截器获取会话ID
获取会话ID的一种方式是添加Spring拦截器,在握手阶段从请求数据中提取信息。可直接在WebSocketConfig
中添加:
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/greeting")
.setHandshakeHandler(new DefaultHandshakeHandler() {
public boolean beforeHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
attributes.put("sessionId", session.getId());
}
return true;
}
}).withSockJS();
}
4. WebSocket接口
从Spring 5.0.5.RELEASE开始,@SendToUser
注解得到改进,无需额外配置即可向用户目的地发送消息。该注解现在支持通过/user/{sessionId}/...
而非/user/{user}/...
路由消息。
这意味着注解会自动依赖输入消息的会话ID,将回复发送到会话私有目的地:
@Controller
public class WebSocketController {
@Autowired
private SimpMessageSendingOperations messagingTemplate;
private Gson gson = new Gson();
@MessageMapping("/message")
@SendToUser("/queue/reply")
public String processMessageFromClient(
@Payload String message,
Principal principal) throws Exception {
return gson
.fromJson(message, Map.class)
.get("name").toString();
}
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
}
关键点:
- ✅
@SendToUser
表示处理方法的返回值将作为消息发送到指定目的地 - ✅ 目的地会自动添加
/user/{username}
前缀 - ⚠️ 异常处理也使用相同机制路由错误消息
5. WebSocket客户端
function connect() {
var socket = new WebSocket('ws://localhost:8080/greeting');
ws = Stomp.over(socket);
ws.connect({}, function(frame) {
ws.subscribe("/user/queue/errors", function(message) {
alert("Error " + message.body);
});
ws.subscribe("/user/queue/reply", function(message) {
alert("Message " + message.body);
});
}, function(error) {
alert("STOMP error " + error);
});
}
function disconnect() {
if (ws != null) {
ws.close();
}
setConnected(false);
console.log("Disconnected");
}
客户端实现要点:
- ✅ 创建指向
/greeting
的WebSocket连接(对应配置中的接口映射) - ✅ 订阅
/user/queue/errors
和/user/queue/reply
- ⚠️ 虽然
@SendToUser
指定的是queue/errors
,但实际消息会发送到/user/queue/errors
6. 总结
本文介绍了使用Spring WebSocket向特定用户或会话发送消息的完整方案。核心技巧是利用改进后的@SendToUser
注解结合会话ID路由机制,避免了复杂的自定义配置。
完整示例代码可在GitHub仓库获取。