註解控制器

應用程式可以使用帶有註解的 @Controller 類來處理來自客戶端的訊息。這些類可以宣告 @MessageMapping@SubscribeMapping@ExceptionHandler 方法,如下面的主題所述

@MessageMapping

您可以使用 @MessageMapping 註解方法,根據訊息的目的地來路由訊息。它支援方法級別和型別級別。在型別級別,@MessageMapping 用於表達控制器中所有方法共享的對映。

預設情況下,對映值是 Ant 風格的路徑模式(例如 /thing*, /thing/**),包括對模板變數(例如 /thing/{id})的支援。這些值可以透過 @DestinationVariable 方法引數引用。正如 點作為分隔符 中解釋的,應用程式也可以切換到使用點分隔的目的地約定進行對映。

支援的方法引數

下表描述了方法引數

方法引數 描述

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 用於將輸出訊息僅傳送給與輸入訊息相關的使用者。參見 使用者目的地

您可以在同一個方法上同時使用 @SendTo@SendToUser,並且它們都支援在類級別使用,在這種情況下它們充當類中方法的預設值。但是請記住,任何方法級別的 @SendTo@SendToUser 註解都會覆蓋類級別的相應註解。

訊息可以非同步處理,@MessageMapping 方法可以返回 ListenableFutureCompletableFutureCompletionStage

請注意,@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 中可用的類似支援相當。