> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/untitled-11/untitled-2/untitled-1.md).

# 18.4.1　在控制器中处理用户的消息

如前所述，在控制器的 @MessageMapping 或 @SubscribeMapping 方法中，处理消息时有两种方式了解用户信息。在处理器方法中，通过简单地添加一个 Principal 参数，这个方法就能知道用户是谁并利用该信息关注此用户相关的数据。除此之外，处理器方法还可以使用 @SendToUser 注解，表明它的返回值要以消息的形式发送给某个认证用户的客户端（只发送给该客户端）。

为了阐述该功能，让我们编写一个控制器方法，它会根据传入的消息创建新的 Spittle 对象，并发送一个回应，表明 Spittle 已经保存成功。如果你觉得这个场景很熟悉的话，那是因为在第 16 章我们以 REST 端点的形式实现了它。但是 REST 请求是同步的，当服务器处理的时候，客户端必须要等待。通过将 Spittle 发送为 STOMP 消息， 我们可以充分发挥 STOMP 消息异步的优势。

考虑如下的 handleSpittle() 方法，它会处理传入的消息并将其存储为 Spittle：

```java
@MessageMapping("/spittle")
@SendToUser("/queue/notifications")
public Notification handleSpittle(
    Principal principal, SpittleForm form) {
  Spittle spittle = new Spittle(principal.getName(), form.getText(), new Date());
  spittleRepo.save(spittle);
  
  return new Notification("Saved Spittle");
}
```

可以看到，handleSpittle() 方法接受 Principal 对象和 SpittleForm 对象作为参数。它使用这两个对象创建一个 Spittle 实例并借助 SpittleRepository 将实例保存起来。最后，它返回一个新的 Notification，表明 Spittle 已经保存成功。

当然，比起方法内部的功能，这个方法体外部所做事情也许更让我们感兴趣。因为这个方法使用了 @MessageMapping 注解，因此当有发往 “/app/spittle” 目的地的消息到达时，该方法就会触发，并且会根据消息创建 SpittleForm 对象，如果用户已经认证过的话，将会根据 STOMP 帧上的头信息得到 Principal 对象。

但是，需要特别关注的是，返回的 Notification 到哪里去了。@SendToUser 注解指定返回的 Notification 要以消息的形式发送到 “/queue/notifications” 目的地上。在表面上，“/queue/notifications” 并没有与特定用户关联。但因为这里使用的是 @SendToUser 注解而不是 @SendTo，所以就会发生更多的事情了。

为了理解 Spring 如何发布消息，让我们先退后一步，看一下针对控制器方法发布 Notification 对象的目的地，客户端该如何进行订阅。考虑如下的这行 JavaScript 代码，它订阅了一个用户特定的目的地：

```java
stomp.subscribe("/user/queue/notifications", handleNotifications);
```

注意，这个目的地使用了 “/user” 作为前缀，在内部，以 “/user” 作为前缀的目的地将会以特殊的方式进行处理。这种消息不会通过  AnnotationMethodMessageHandler（像应用消息那样）来处理，也不会通过 SimpleBrokerMessageHandler 或 StompBrokerRelayMessageHandler（像代理消息那样）来处理，以 “/user” 为前缀的消息将会通过 UserDestinationMessageHandler 进行处理，如图 18.4 所示。

![图 18.4 用户消息流会通过 UserDestinationMessageHandler 进行处理，它会将消息重路由到某个用户独有的目的地上](https://3784153818-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmcjU5gG__lBrRbUxBO%2F-LnvOlqTj1_vF0t2hJRp%2F-LnvQtZ0hvghyuHWO-yb%2F18.4%20%E7%94%A8%E6%88%B7%E6%B6%88%E6%81%AF%E6%B5%81.jpg?alt=media\&token=a7f57f1e-f349-4bbb-abfd-aabd371cda78)

UserDestinationMessageHandler 的主要任务是将用户消息重新路由到某个用户独有的目的地上。在处理订阅的时候，它会将目标地址中的 “/user” 前缀去掉，并基于用户的会话添加一个后缀。例如，对 “/user/queue/notifications” 的订阅最后可能路由到名为 “/queue/notifications-user6hr83v6t” 的目的地上。

在我们的样例中，handleSpittle() 方法使用了 @SendToUser("/queue/notifications") 注解。这个新的目的地以 “/queue” 作为前缀，根据配置，这是 StompBrokerRelayMessageHandler（或 SimpleBrokerMessageHandler 要处理的前缀，所以消息接下来会到达这里。最终，客户端会订阅这个目的地，因此客户端会收到 Notification 消息。

在控制器方法中，@SendToUser 注解和 Principal 参数是很有用的。但是在程序清单 18.8 中，我们看到借助消息模板，可以在应用的任何位置发送消息。接下来看一下如何使用 SimpMessagingTemplate 将消息发送给特定用户。
