Annotated Controllers

@MessageMapping 注释允许控制器映射消息目标,而 @SubscribeMapping 专门用于处理订阅。@MessageExceptionHandler 方法捕获并处理 @MessageMapping 方法中的异常。通过这些注释和方法参数,应用程序可以定制消息处理、返回值和异常处理。

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

@MessageMapping

您可以使用 @MessageMapping 为基于目标路由消息的方法加上注释。它在方法级别和类型级别都受支持。在类型级别,@MessageMapping 用于表示控制器中所有方法的共享映射。

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

Supported Method Arguments

下表描述了方法参数:

Method argument Description

Message

要访问完整消息。

MessageHeaders

要访问 Message 中的头。

MessageHeaderAccessor, SimpMessageHeaderAccessor, and StompHeaderAccessor

要通过类型访问器方法访问头。

@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 握手时登录的用户。

Return Values

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

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

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

消息可以异步处理,并且`@MessageMapping`方法可以返回`ListenableFuture`、CompletableFuture`或`CompletionStage

请注意,@SendTo`和 `@SendToUser`只是方便起见,相当于使用 `SimpMessagingTemplate`来发送邮件。如有必要,对于更高级的场景,@MessageMapping`方法可以回退至直接使用 SimpMessagingTemplate。可以执行此操作来代替返回一个值,或者在返回一个值之外执行此操作。请参阅 Sending Messages

@SubscribeMapping

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

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

这样的情况有什么用?除非你想让代理和控制器独立处理消息(包括订阅),否则不要尝试将代理和控制器映射到同一个目标前缀。入站消息会以并行方式进行处理。无法保证代理或控制器会优先处理哪条消息。如果目的是在存储并准备广播订阅时收到通知,那么客户端应该要求回执(如果服务器支持此操作,简单代理不支持)。例如,对于 Java STOMP client,你可以执行以下操作来添加回执:

@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...
});

服务器端的选项是在 brokerChannel`中添加一个 `ExecutorChannelInterceptor,并实现处理消息(包括订阅)后调用的 `afterMessageHandled`方法。

@MessageExceptionHandler

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

@Controller
public class MyController {

	// ...

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

@MessageExceptionHandler 方法支持灵活的方法签名,并且支持与 xref:web/websocket/stomp/handle-annotations.adoc#websocket-stomp-message-mapping[@MessageMapping 方法相同的方法参数类型和返回值。

通常情况下,`@MessageExceptionHandler`方法适用于其所属的 `@Controller`类(或类层次结构)中。如果你希望此类方法应用于更广的范围(跨控制器),可以在用 `@ControllerAdvice`标记的类中声明这些方法。这与 Spring MVC 中可用的 similar support类似。