用户目的地 (User Destinations)
在实时应用中,经常需要向特定用户(而不是所有人)发送消息。Spring 的 STOMP 支持通过 /user/ 前缀识别这类目的地。
客户端订阅
客户端可以订阅以 /user/ 为前缀的目的地。 例如:订阅 /user/queue/position-updates。
服务器端的 UserDestinationMessageHandler 会捕获此类订阅,并将其转换为该用户会话特有的唯一目的地(例如 /queue/position-updates-user123)。这避免了不同用户订阅相同名称的主题时发生消息冲突(即每个用户收到的都是属于自己的仓位更新)。
TIP
使用用户目的地时,务必正确配置代理前缀(Broker Prefixes)和应用目的地前缀。否则,如果不匹配,消息可能会错误地直接飞到外部代理而没被 Spring 拦截处理。
服务器端发送
服务器端组件可以向 /user/{username}/queue/position-updates 发送消息。UserDestinationMessageHandler 会将其解析为该用户关联的所有会话(Session)对应的真实目的地。
1. 使用 @SendToUser 注解
在控制器方法中,你可以使用此注解将返回值直接发送给当前用户:
@Controller
public class PortfolioController {
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// 交易逻辑...
return tradeResult;
}
}broadcast属性: 默认值为true(如果用户在多个浏览器页签登录,所有页签都会收到)。设为false则仅发送给引发当前请求的那个特定会话。
2. 使用 SimpMessagingTemplate
你可以在任何地方(如 Service)编程式地发送:
@Service
public class TradeServiceImpl implements TradeService {
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void afterTradeExecuted(Trade trade) {
this.messagingTemplate.convertAndSendToUser(
trade.getUserName(), "/queue/position-updates", trade.getResult());
}
}补充教学
1. 它是如何区分不同用户的?
它依赖于 java.security.Principal 对象。如果你的应用没有通过 Spring Security 登录,Spring 会为每个会话生成一个匿名的随机 ID 替代。
2. 多节点环境下的挑战
如果用户 A 连在 Server 1,而你的发送指令在 Server 2 执行,Server 2 必须能够知道用户 A 在别处。因此,在分布式环境中,你可能需要配置 userDestinationBroadcast 属性,通过外部代理(如 RabbitMQ)广播这种“找人”请求。
3. 与外部代理协作时的队列清理
在使用 RabbitMQ 时,这种动态生成的目的地(如 /queue/position-updates-user123)可能产生临时队列。你应该利用 RabbitMQ 的 auto-delete 队列特性,确保用户断开后,这些私有队列被自动销毁。