注解控制器

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

@MessageMapping

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

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

支持的方法参数

以下表格描述了方法参数:

方法参数 描述

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用于将输出消息仅发送给与输入消息关联的用户。请参阅用户目的地

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

这在什么情况下不有用?不要尝试将代理和控制器映射到相同的目的地前缀,除非出于某种原因希望两者独立处理消息,包括订阅。入站消息是并行处理的。不能保证代理或控制器哪个先处理给定的消息。如果目标是在订阅存储并准备进行广播时收到通知,客户端应该在服务器支持时请求回执(简单代理不支持)。例如,使用Java STOMP客户端,您可以执行以下操作添加回执:

@Autowired
private TaskScheduler messageBrokerTaskScheduler;

// 初始化时..
stompClient.setTaskScheduler(this.messageBrokerTaskScheduler);

// 订阅时..
StompHeaders headers = new StompHeaders();
headers.setDestination("/topic/...");
headers.setReceipt("r1");
FrameHandler handler = ...;
stompSession.subscribe(headers, handler).addReceiptTask(receiptHeaders -> {
	// 订阅准备就绪...
});

服务器端选项是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中提供的类似支持