注解控制器
应用程序可以使用带有注解的 @Controller
类来处理来自客户端的消息。这些类可以声明 @MessageMapping
、@SubscribeMapping
和 @ExceptionHandler
方法,如下面的主题所述
@MessageMapping
您可以使用 @MessageMapping
注解方法,根据消息的目的地来路由消息。它支持方法级别和类型级别。在类型级别,@MessageMapping
用于表达控制器中所有方法共享的映射。
默认情况下,映射值是 Ant 风格的路径模式(例如 /thing*
, /thing/**
),包括对模板变量(例如 /thing/{id}
)的支持。这些值可以通过 @DestinationVariable
方法参数引用。正如 点作为分隔符 中解释的,应用程序也可以切换到使用点分隔的目的地约定进行映射。
支持的方法参数
下表描述了方法参数
方法参数 | 描述 |
---|---|
|
用于访问完整消息。 |
|
用于访问 |
|
用于通过类型化的访问器方法访问头部。 |
|
用于访问消息的有效载荷,它会被配置好的 此注解并非必须存在,因为它在没有其他参数匹配时默认被假定存在。 您可以使用 |
|
用于访问特定的头部值 — 如果需要,结合使用 |
|
用于访问消息中的所有头部。此参数必须可赋值给 |
|
用于访问从消息目的地提取的模板变量。必要时,值会被转换为声明的方法参数类型。 |
|
反映 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
,但仅将映射范围缩小到订阅消息。它支持与 @MessageMapping
相同的方法参数。然而对于返回值,默认情况下,消息是直接发送给客户端(通过 clientOutboundChannel
,作为对订阅的回应),而不是发送给 broker(通过 brokerChannel
,作为对匹配订阅的广播)。添加 @SendTo
或 @SendToUser
会覆盖此行为,转而发送给 broker。
这什么时候有用?假设 broker 映射到 /topic
和 /queue
,而应用控制器映射到 /app
。在这种设置下,broker 会存储所有旨在重复广播到 /topic
和 /queue
的订阅,应用程序无需介入。客户端也可以订阅某个 /app
目的地,并且控制器可以对该订阅返回一个值作为响应,而无需涉及 broker,也无需再次存储或使用该订阅(实质上是一次性的请求-回复交换)。一个用例是在启动时用初始数据填充 UI。
这什么时候没用?除非您出于某种原因希望 broker 和控制器都独立处理消息(包括订阅),否则不要尝试将它们映射到同一个目的地前缀。入站消息是并行处理的。无法保证 broker 或控制器哪个先处理给定消息。如果目标是在订阅存储并准备好广播时收到通知,如果服务器支持(简单 broker 不支持),客户端应该请求回执。例如,使用 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...
});
服务器端的一种选择是在 brokerChannel
上注册一个 ExecutorChannelInterceptor
,并实现 afterMessageHandled
方法,该方法会在处理完消息(包括订阅)后被调用。
@MessageExceptionHandler
应用程序可以使用 @MessageExceptionHandler
方法来处理 @MessageMapping
方法抛出的异常。您可以在注解本身中声明异常,或者如果想访问异常实例,可以通过方法参数声明。以下示例通过方法参数声明异常:
@Controller
public class MyController {
// ...
@MessageExceptionHandler
public ApplicationError handleException(MyException exception) {
// ...
return appError;
}
}
@MessageExceptionHandler
方法支持灵活的方法签名,并支持与 @MessageMapping
方法相同的方法参数类型和返回值。
通常,@MessageExceptionHandler
方法仅应用于声明它们的 @Controller
类(或类层次结构)内。如果您希望这些方法应用得更全局(跨控制器),可以在用 @ControllerAdvice
标记的类中声明它们。这与 Spring MVC 中可用的类似支持相当。