带注释的控制器

应用程序可以使用带注解的 @Controller 类来处理来自客户端的消息。这些类可以声明 @MessageMapping@SubscribeMapping@ExceptionHandler 方法,如以下主题所述

@MessageMapping

您可以使用 @MessageMapping 来注解根据其目标路由消息的方法。它支持方法级别和类型级别。在类型级别,@MessageMapping 用于表达控制器中所有方法的共享映射。

默认情况下,映射值是 Ant 风格的路径模式(例如 /thing*/thing/**),包括对模板变量的支持(例如,/thing/{id})。这些值可以通过 @DestinationVariable 方法参数引用。应用程序还可以切换到点分隔的目标约定进行映射,如 点作为分隔符 中所述。

支持的方法参数

下表描述了方法参数

方法参数 描述

Message

用于访问完整的消息。

MessageHeaders

用于访问Message中的标头。

MessageHeaderAccessorSimpMessageHeaderAccessorStompHeaderAccessor

用于通过类型化访问器方法访问标头。

@Payload

用于访问消息的有效负载,该有效负载由配置的MessageConverter转换(例如,从 JSON 转换)。

如果未匹配其他参数,则默认情况下会假定此注释的存在,因此不需要此注释。

您可以使用@jakarta.validation.Valid或 Spring 的@Validated注释有效负载参数,以使有效负载参数自动验证。

@Header

用于访问特定标头值,以及使用org.springframework.core.convert.converter.Converter进行类型转换(如果需要)。

@Headers

用于访问消息中的所有标头。此参数必须可分配给java.util.Map

@DestinationVariable

用于访问从消息目标中提取的模板变量。根据需要将值转换为声明的方法参数类型。

java.security.Principal

反映 WebSocket HTTP 握手时登录的用户。

返回值

默认情况下,@MessageMapping方法的返回值将通过匹配的MessageConverter序列化为有效负载,并作为Message发送到brokerChannel,从那里广播到订阅者。出站消息的目标与入站消息的目标相同,但以/topic为前缀。

您可以使用@SendTo@SendToUser注释自定义输出消息的目标。@SendTo用于自定义目标目标或指定多个目标。@SendToUser用于将输出消息仅定向到与输入消息关联的用户。请参阅用户目标

您可以在同一方法上同时使用@SendTo@SendToUser,并且两者都支持在类级别使用,在这种情况下,它们充当类中方法的默认值。但是,请记住,任何方法级别的@SendTo@SendToUser注释都会覆盖类级别的任何此类注释。

消息可以异步处理,@MessageMapping 方法可以返回 ListenableFutureCompletableFutureCompletionStage

请注意,@SendTo@SendToUser 只是使用 SimpMessagingTemplate 发送消息的便捷方式。如果需要,对于更高级的场景,@MessageMapping 方法可以直接使用 SimpMessagingTemplate。这可以代替或可能除了返回一个值之外。请参阅 发送消息

@SubscribeMapping

@SubscribeMapping@MessageMapping 类似,但将映射范围缩小到仅订阅消息。它支持与 @MessageMapping 相同的 方法参数。但是,对于返回值,默认情况下,消息会直接发送到客户端(通过 clientOutboundChannel,作为对订阅的响应),而不是发送到代理(通过 brokerChannel,作为对匹配订阅的广播)。添加 @SendTo@SendToUser 会覆盖此行为,并改为发送到代理。

这在什么时候有用?假设代理映射到 /topic/queue,而应用程序控制器映射到 /app。在此设置中,代理存储所有对 /topic/queue 的订阅,这些订阅用于重复广播,并且应用程序无需参与。客户端还可以订阅一些 /app 目标,并且控制器可以在响应该订阅时返回一个值,而无需涉及代理,也无需再次存储或使用订阅(实际上是一次性请求-回复交换)。此功能的一个用例是在启动时使用初始数据填充 UI。

什么时候不实用?除非出于某些原因,您希望代理和控制器独立处理消息(包括订阅),否则不要尝试将代理和控制器映射到相同的目标前缀。入站消息是并行处理的。无法保证代理或控制器会先处理给定消息。如果目标是在存储订阅并准备广播时收到通知,则客户端应在服务器支持的情况下请求回执(简单代理不支持)。例如,使用 Java STOMP 客户端,您可以执行以下操作来添加回执

@Autowired
private TaskScheduler messageBrokerTaskScheduler;

// During initialization..
stompClient.setTaskScheduler(this.messageBrokerTaskScheduler);

// When subscribing..
StompHeaders headers = new StompHeaders();
headers.setDestination("/topic/...");
headers.setReceipt("r1");
FrameHandler handler = ...;
stompSession.subscribe(headers, handler).addReceiptTask(receiptHeaders -> {
	// Subscription ready...
});

服务器端选项是 注册 一个 ExecutorChannelInterceptorbrokerChannel 上并实现 afterMessageHandled 方法,该方法在处理消息(包括订阅)后调用。

@MessageExceptionHandler

应用程序可以使用 @MessageExceptionHandler 方法处理来自 @MessageMapping 方法的异常。您可以在注释本身或通过方法参数声明异常,如果您想访问异常实例。以下示例通过方法参数声明异常

@Controller
public class MyController {

	// ...

	@MessageExceptionHandler
	public ApplicationError handleException(MyException exception) {
		// ...
		return appError;
	}
}

@MessageExceptionHandler 方法支持灵活的方法签名,并支持与 @MessageMapping 方法相同的参数类型和返回值。

通常,@MessageExceptionHandler 方法应用于声明它们的 @Controller 类(或类层次结构)中。如果您希望这些方法更全局地应用(跨控制器),您可以在标记为 @ControllerAdvice 的类中声明它们。这与 Spring MVC 中 类似的支持 相当。