注解控制器

应用程序可以使用带有注解的 @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 中可用的 类似支持 相当。