跳到主要内容

注释控制器

ChatGPT-4o-mini 中英对照 Annotated Controllers

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

@MessageMapping

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

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

支持的方法参数

下表描述了方法参数:

方法参数描述
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 用于将输出消息仅定向到与输入消息相关联的用户。请参见 User Destinations

您可以在同一个方法上同时使用 @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 目的地,控制器可以在不涉及代理的情况下响应该订阅并返回一个值,而无需再次存储或使用该订阅(实际上是一次性请求-响应交换)。一个使用案例是在启动时用初始数据填充用户界面。

何时这没有用?除非你希望两个独立处理消息(包括订阅)的原因,否则不要尝试将代理和控制器映射到相同的目标前缀。入站消息是并行处理的。没有保证代理或控制器会先处理给定的消息。如果目标是当订阅被存储并准备好广播时收到通知,客户端应该请求收据(如果服务器支持的话,简单代理不支持)。例如,使用 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...
});
java

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

@MessageExceptionHandler

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

@Controller
public class MyController {

// ...

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

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

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