|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
带注解的控制器
应用程序可以使用带注解的 @Controller 类来处理来自客户端的消息。
此类可以声明 @MessageMapping、@SubscribeMapping 和 @ExceptionHandler
方法,如下文各节所述:
@MessageMapping
你可以使用 @MessageMapping 注解来标注那些根据消息目标(destination)进行路由的方法。该注解既支持在方法级别使用,也支持在类级别使用。在类级别上,@MessageMapping 用于表示控制器中所有方法共享的映射。
默认情况下,映射值采用 Ant 风格的路径模式(例如 /thing*、/thing/**),
并支持模板变量(例如 /thing/{id})。这些值可以通过 @DestinationVariable 方法参数进行引用。
应用程序也可以切换为使用点号分隔的目标地址约定来进行映射,如
以点号作为分隔符 中所述。
支持的方法参数
下表描述了方法参数:
| 方法参数 | <description> </description> |
|---|---|
|
用于访问完整消息。 |
|
用于访问 |
|
通过类型化的访问器方法来访问头部信息。 |
|
用于访问消息的有效载荷,该有效载荷由已配置的 由于在没有其他参数匹配时,默认会假定存在此注解,因此该注解的显式声明并非必需。 您可以使用 |
|
用于访问特定的请求头值——必要时,可配合使用 |
|
用于访问消息中的所有标头。该参数必须可赋值给 |
|
用于访问从消息目标中提取的模板变量。 必要时,这些值会转换为所声明的方法参数类型。 |
|
反映在 WebSocket HTTP 握手时登录的用户。 |
返回值
默认情况下,@MessageMapping 方法的返回值会通过匹配的 MessageConverter 序列化为消息负载,并作为 Message 发送到 brokerChannel,然后从该通道广播给订阅者。出站消息的目标地址与入站消息的目标地址相同,但会加上 /topic 前缀。
您可以使用 @SendTo 和 @SendToUser 注解来自定义输出消息的目标。
@SendTo 用于自定义目标地址或指定多个目标地址。
@SendToUser 用于将输出消息仅发送给与输入消息关联的用户。请参阅用户目标地址。
你可以在同一个方法上同时使用 @SendTo 和 @SendToUser,并且这两个注解在类级别上也受支持,在这种情况下,它们将作为该类中方法的默认设置。但请注意,任何方法级别的 @SendTo 或 @SendToUser 注解都会覆盖类级别上的相应注解。
消息可以异步处理,@MessageMapping 方法可以返回 ListenableFuture、CompletableFuture 或 CompletionStage。
请注意,@SendTo 和 @SendToUser 仅是一种便捷方式,其本质是使用
SimpMessagingTemplate 来发送消息。在更高级的场景中,如有必要,
@MessageMapping 方法可以直接回退到使用 SimpMessagingTemplate。
这可以替代返回值的方式,或者也可能与返回值同时使用。
参见发送消息。
@SubscribeMapping
@SubscribeMapping 与 @MessageMapping 类似,但其映射范围仅限于订阅消息。它支持与 #websocket-stomp-message-mapping 相同的方法参数。然而,对于返回值,默认情况下,消息会直接发送给客户端(通过 clientOutboundChannel,作为对订阅的响应),而不是发送给代理(通过 brokerChannel,向匹配的订阅进行广播)。添加 @SendTo 或 @SendToUser 注解将覆盖此默认行为,改为将消息发送给代理。
这在什么情况下有用呢?假设代理(broker)被映射到 /topic 和 /queue,而应用程序控制器则被映射到 /app。在此设置下,代理会存储所有针对 /topic 和 /queue 的订阅,这些订阅用于重复广播,应用程序无需参与其中。客户端也可以订阅某个 /app 目的地,此时控制器可以针对该订阅直接返回一个值,而无需代理介入,也无需存储或再次使用该订阅(本质上是一次性的请求-响应交互)。这种模式的一个典型用例是在应用启动时为用户界面填充初始数据。
何时这种做法不适用?除非出于某种原因,你希望代理(broker)和控制器(controller)都能独立处理消息(包括订阅消息),否则不要尝试将代理和控制器映射到相同的目标前缀。入站消息是并行处理的,无法保证某个特定消息会先由代理还是控制器处理。如果目标是在订阅被存储并准备好接收广播时收到通知,客户端应请求回执(receipt),前提是服务器支持该功能(简单代理不支持)。例如,使用 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 中提供的类似支持相当。