注释控制器
应用程序可以使用带注解的 @Controller
类来处理来自客户端的消息。这些类可以声明 @MessageMapping
、@SubscribeMapping
和 @ExceptionHandler
方法,如下文所述:
@MessageMapping
您可以使用 @MessageMapping
注解方法,以根据消息的目的地进行路由。它支持方法级别和类型级别。在类型级别,@MessageMapping
用于表示控制器中所有方法的共享映射。
默认情况下,映射值是 Ant 风格的路径模式(例如 /thing*
,/thing/**
),包括对模板变量的支持(例如 /thing/{id}
)。这些值可以通过 @DestinationVariable
方法参数进行引用。应用程序还可以切换到以点分隔的目标约定进行映射,如 Dots as Separators 中所述。
支持的方法参数
下表描述了方法参数:
方法参数 | 描述 |
---|---|
Message | 用于访问完整的消息。 |
MessageHeaders | 用于访问 Message 中的头部信息。 |
MessageHeaderAccessor 、SimpMessageHeaderAccessor 和 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 握手时登录的用户。 |
返回值
默认情况下,@MessageMapping
方法的返回值通过匹配的 MessageConverter
序列化为有效负载,并作为 Message
发送到 brokerChannel
,然后从那里广播给订阅者。出站消息的目标与入站消息相同,但前面加上 /topic
。
您可以使用 @SendTo
和 @SendToUser
注解来自定义输出消息的目标。 @SendTo
用于自定义目标目的地或指定多个目的地。 @SendToUser
用于将输出消息仅定向到与输入消息相关联的用户。请参见 User Destinations。
您可以在同一个方法上同时使用 @SendTo
和 @SendToUser
,并且它们在类级别也受到支持,在这种情况下,它们作为类中方法的默认值。然而,请记住,任何方法级别的 @SendTo
或 @SendToUser
注解会覆盖类级别的任何此类注解。
消息可以异步处理,@MessageMapping
方法可以返回 ListenableFuture
、CompletableFuture
或 CompletionStage
。
注意 @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...
});
一个服务器端的选项是 注册 一个 ExecutorChannelInterceptor
到 brokerChannel
,并实现 afterMessageHandled
方法,该方法在消息(包括订阅)被处理后被调用。
@MessageExceptionHandler
一个应用程序可以使用 @MessageExceptionHandler
方法来处理来自 @MessageMapping
方法的异常。您可以在注解本身中声明异常,或者通过方法参数来声明,如果您想访问异常实例。以下示例通过方法参数声明了一个异常:
@Controller
public class MyController {
// ...
@MessageExceptionHandler
public ApplicationError handleException(MyException exception) {
// ...
return appError;
}
}
@MessageExceptionHandler
方法支持灵活的方法签名,并支持与 @MessageMapping 方法相同的方法参数类型和返回值。
通常,@MessageExceptionHandler
方法适用于声明它们的 @Controller
类(或类层次结构)中。如果您希望这些方法更广泛地适用(跨控制器),可以在标记为 @ControllerAdvice
的类中声明它们。这与 Spring MVC 中可用的 类似支持 相当。