18.4.1 在控制器中处理用户的消息
Last updated
Last updated
如前所述,在控制器的 @MessageMapping 或 @SubscribeMapping 方法中,处理消息时有两种方式了解用户信息。在处理器方法中,通过简单地添加一个 Principal 参数,这个方法就能知道用户是谁并利用该信息关注此用户相关的数据。除此之外,处理器方法还可以使用 @SendToUser 注解,表明它的返回值要以消息的形式发送给某个认证用户的客户端(只发送给该客户端)。
为了阐述该功能,让我们编写一个控制器方法,它会根据传入的消息创建新的 Spittle 对象,并发送一个回应,表明 Spittle 已经保存成功。如果你觉得这个场景很熟悉的话,那是因为在第 16 章我们以 REST 端点的形式实现了它。但是 REST 请求是同步的,当服务器处理的时候,客户端必须要等待。通过将 Spittle 发送为 STOMP 消息, 我们可以充分发挥 STOMP 消息异步的优势。
考虑如下的 handleSpittle() 方法,它会处理传入的消息并将其存储为 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 代码,它订阅了一个用户特定的目的地:
注意,这个目的地使用了 “/user” 作为前缀,在内部,以 “/user” 作为前缀的目的地将会以特殊的方式进行处理。这种消息不会通过 AnnotationMethodMessageHandler(像应用消息那样)来处理,也不会通过 SimpleBrokerMessageHandler 或 StompBrokerRelayMessageHandler(像代理消息那样)来处理,以 “/user” 为前缀的消息将会通过 UserDestinationMessageHandler 进行处理,如图 18.4 所示。
UserDestinationMessageHandler 的主要任务是将用户消息重新路由到某个用户独有的目的地上。在处理订阅的时候,它会将目标地址中的 “/user” 前缀去掉,并基于用户的会话添加一个后缀。例如,对 “/user/queue/notifications” 的订阅最后可能路由到名为 “/queue/notifications-user6hr83v6t” 的目的地上。
在我们的样例中,handleSpittle() 方法使用了 @SendToUser("/queue/notifications") 注解。这个新的目的地以 “/queue” 作为前缀,根据配置,这是 StompBrokerRelayMessageHandler(或 SimpleBrokerMessageHandler 要处理的前缀,所以消息接下来会到达这里。最终,客户端会订阅这个目的地,因此客户端会收到 Notification 消息。
在控制器方法中,@SendToUser 注解和 Principal 参数是很有用的。但是在程序清单 18.8 中,我们看到借助消息模板,可以在应用的任何位置发送消息。接下来看一下如何使用 SimpMessagingTemplate 将消息发送给特定用户。