对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

用户目标地址

应用程序可以发送针对特定用户的消息,而 Spring 的 STOMP 支持通过识别以 /user/ 为前缀的目的地来实现此功能。 例如,客户端可能会订阅 /user/queue/position-updates 目的地。 UserDestinationMessageHandler 负责处理该目的地,并将其转换为用户会话独有的目的地(例如 /queue/position-updates-user123)。 这样既提供了订阅通用名称目的地的便利性,又能确保与其他订阅相同目的地的用户之间不会发生冲突,从而使每位用户都能接收到专属的股票持仓更新。spring-doc.cadn.net.cn

在使用用户目标(user destinations)时,必须按照启用 STOMP中所示的方式配置代理(broker)和应用程序的目标前缀,否则代理将会处理以“/user”为前缀的消息,而这些消息本应仅由UserDestinationMessageHandler处理。

在发送端,消息可以被发送到一个目标地址,例如 /user/{username}/queue/position-updates,该地址随后会被 UserDestinationMessageHandler 转换为一个或多个具体的目标地址,每个与该用户关联的会话对应一个目标地址。这使得应用程序中的任何组件都可以向特定用户发送消息,而无需了解除用户名和通用目标地址之外的更多信息。此功能也通过注解和消息模板得到支持。spring-doc.cadn.net.cn

消息处理方法可以通过 @SendToUser 注解(也可在类级别上使用,以共享一个通用目标地址)向与当前处理消息关联的用户发送消息,如下例所示:spring-doc.cadn.net.cn

@Controller
public class PortfolioController {

	@MessageMapping("/trade")
	@SendToUser("/queue/position-updates")
	public TradeResult executeTrade(Trade trade, Principal principal) {
		// ...
		return tradeResult;
	}
}

如果用户拥有多个会话,默认情况下,所有订阅了指定目标的会话都会被作为目标。然而,有时可能需要仅将处理中的消息所来自的那个会话作为目标。您可以通过将 broadcast 属性设置为 false 来实现这一点,如下例所示:spring-doc.cadn.net.cn

@Controller
public class MyController {

	@MessageMapping("/action")
	public void handleAction() throws Exception{
		// raise MyBusinessException here
	}

	@MessageExceptionHandler
	@SendToUser(destinations="/queue/errors", broadcast=false)
	public ApplicationError handleException(MyBusinessException exception) {
		// ...
		return appError;
	}
}
虽然用户目标(user destinations)通常意味着已认证的用户,但这并不是严格必需的。 一个未与已认证用户关联的 WebSocket 会话也可以订阅用户目标。 在这种情况下,@SendToUser 注解的行为与设置 broadcast=false 时完全相同(即仅针对处理消息的发送会话)。

你可以通过注入由 Java 配置或 XML 命名空间创建的 SimpMessagingTemplate,从任意应用程序组件向用户目标发送消息。(如果需要使用 brokerMessagingTemplate 进行限定,该 bean 的名称为 @Qualifier。)以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@Service
public class TradeServiceImpl implements TradeService {

	private final SimpMessagingTemplate messagingTemplate;

	@Autowired
	public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) {
		this.messagingTemplate = messagingTemplate;
	}

	// ...

	public void afterTradeExecuted(Trade trade) {
		this.messagingTemplate.convertAndSendToUser(
				trade.getUserName(), "/queue/position-updates", trade.getResult());
	}
}
当你在使用外部消息代理(message broker)配合用户目的地(user destinations)时,应查阅该代理的文档,了解如何管理非活跃队列,以便在用户会话结束时,自动移除所有唯一的用户队列。例如,当你使用类似 /exchange/amq.direct/position-updates 的目的地时,RabbitMQ 会创建自动删除(auto-delete)队列。因此,在这种情况下,客户端可以订阅 /user/exchange/amq.direct/position-updates。 同样地,ActiveMQ 也提供了 配置选项 用于清理非活跃的目的地。

在多应用服务器场景中,用户目标可能因用户连接到不同的服务器而无法解析。在这种情况下,您可以配置一个目标,以广播未解析的消息,使其他服务器有机会尝试处理。这可以通过 Java 配置中 userDestinationBroadcastMessageBrokerRegistry 属性,或 XML 中 user-destination-broadcast 元素的 message-broker 属性来实现。spring-doc.cadn.net.cn