返回介绍

18.3.2 处理来自客户端的 STOMP 消息

发布于 2024-08-17 00:45:49 字数 4144 浏览 0 评论 0 收藏 0

我们在第5章已经学习过,Spring MVC为处理HTTP Web请求提供了面向注解的编程模型。@RequestMapping是Spring MVC中最著名的注解,它会将HTTP请求映射到对请求进行处理的方法上。在第16章,我们也曾经看到相同的编程模型扩展到了RESTful的资源处理中。

STOMP和WebSocket更多的是关于异步消息,与HTTP的请求-响应方式有所不同。但是,Spring提供了非常类似于Spring MVC的编程模型来处理STOMP消息。它非常地相似,以至于对STOMP消息的处理器方法也会包含在带有@Controller注解的类中。

Spring 4.0引入了@MessageMapping注解,它用于STOMP消息的处理,类似于Spring MVC的@RequestMapping注解。当消息抵达某个特定的目的地时,带有@MessageMapping注解的方法能够处理这些消息。例如,考虑如下程序清单中的控制器类。

程序清单18.6 借助@MessageMapping注解能够在控制器中处理STOMP消息

乍一看上去,它非常类似于其他的Spring MVC控制器类。它使用了@Controller注解,所以组件扫描能够找到它并将其注册为bean。就像其他的@Controller类一样,它也包含了处理器方法。

但是这个处理器方法与我们之前看到的有一点区别。handleShout()方法没有使用@RequestMapping注解,而是使用了@MessageMapping注解。这表示handleShout()方法能够处理指定目的地上到达的消息。在本例中,这个目的地也就是“/app/marco”(“/app”前缀是隐含的,因为我们将其配置为应用的目的地前缀)。

因为handleShout()方法接收一个Shout参数,所以Spring的某一个消息转换器会将STOMP消息的负载转换为Shout对象。Shout类非常简单,它是只具有一个属性的JavaBean,包含了消息的内容:

因为我们现在处理的不是HTTP,所以无法使用Spring的HttpMessageConverter实现将负载转换为Shout对象。Spring 4.0提供了几个消息转换器,作为其消息API的一部分。表18.1描述了这些消息转换器,在处理STOMP消息的时候可能会用到它们。

表18.1 Spring能够使用某一个消息转换器将消息负载转换为Java类型

消息转换器

描  述

ByteArrayMessageConverter

实现MIME类型为“application/octet-stream”的消息与byte[]之间的相互转换

MappingJackson2MessageConverter

实现MIME类型为“application/json”的消息与Java对象之间的相互转换

StringMessageConverter

实现MIME类型为“text/plain”的消息与String之间的相互转换

假设handleShout()方法所处理消息的内容类型为“application/json”(这应该是一个安全的假设,因为Shout不是byte[]和String),MappingJackson2MessageConverter会负责将JSON消息转换为Shout对象。就像在HTTP中对应的MappingJackson2HttpMessageConverter一样,MappingJackson2MessageConverter会将其任务委托给底层的Jackson 2 JSON处理器。默认情况下,Jackson会使用反射将JSON属性映射为Java对象的属性。尽管在本例中没有必要,但是我们可以通过在Java类型上使用Jackson注解,影响具体的转换行为。

处理订阅

除了@MessagingMapping注解以外,Spring还提供了@SubscribeMapping注解。与@MessagingMapping注解方法类似,当收到STOMP订阅消息的时候,带有@SubscribeMapping注解的方法将会触发。

很重要的一点,与@MessagingMapping方法类似,@SubscribeMapping方法也是通过AnnotationMethodMessageHandler接收消息的(如图18.2和图18.3所示)。按照程序清单18.5的配置,这就意味着@SubscribeMapping方法只能处理目的地以“/app”为前缀的消息。

这可能看上去有些诡异,因为应用发出的消息都会经过代理,目的地要以“/topic”或“/queue”打头。客户端会订阅这些目的地,而不会订阅前缀为“/app”的目的地。如果客户端订阅“/topic”和“/queue”这样的目的地,那么@SubscribeMapping方法也就无法处理这样的订阅了。如果是这样的话,@SubscribeMapping有什么用处呢?

@SubscribeMapping的主要应用场景是实现请求-回应模式。在请求-回应模式中,客户端订阅某一个目的地,然后预期在这个目的地上获得一个一次性的响应。

例如,考虑如下@SubscribeMapping注解标注的方法:

可以看到,handleSubscription()方法使用了@SubscribeMapping注解,用这个方法来处理对“/app/marco”目的地的订阅(与@MessageMapping类似,“/app”是隐含的)。当处理这个订阅时,handleSubscription()方法会产生一个输出的Shout对象并将其返回。然后,Shout对象会转换成一条消息,并且会按照客户端订阅时相同的目的地发送回客户端。

如果你觉得这种请求-回应模式与HTTP GET的请求-响应模式并没有太大差别的话,那么你基本上是正确的。但是,这里的关键区别在于HTTP GET请求是同步的,而订阅的请求-回应模式则是异步的,这样客户端能够在回应可用时再去处理,而不必等待。

编写JavaScript客户端

handleShout()方法已经可以处理发送过来的消息了。现在,我们需要的就是发送消息的客户端。

如下的程序清单展现了一些JavaScript客户端代码,它会连接“/marcopolo”端点并发送“Marco!”消息。

程序清单18.7 借助STOMP库,通过JavaScript发送消息

与我们之前的JavaScript客户端样例类似,在这里首先针对给定的URL创建一个SockJS实例。在本例中,URL引用的是程序清单18.5中所配置的STOMP端点(不包括应用的上下文路径“/stomp”)。

但是,这里的区别在于,我们不再直接使用SockJS,而是通过调用Stomp.over(sock)创建了一个STOMP客户端实例。这实际上封装了SockJS,这样就能在WebSocket连接上发送STOMP消息。

接下来,我们使用STOMP进行连接,假设连接成功,然后发送带有JSON负载的消息到名为“/marco”的目的地。往send()方法传递的第二个参数是一个头信息的Map,它会包含在STOMP的帧中,不过在这个例子中,我们没有提供任何参数,Map是空的。

现在,我们有了能够发送消息到服务器的客户端,以及用来处理消息的服务端处理器方法。这是一个好的开端,但是你可能已经发现这都是单向的。接下来,我们让服务器发出的声音,看一下如何发送消息给客户端。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文