一旦 STOMP 端点被暴露,Spring 应用程序就成为连接客户端的 STOMP 代理。本节描述了服务器端的消息流。
spring-messaging 模块包含了对消息传递应用的基础支持,这些支持起源于 Spring Integration,后来被提取并整合到 Spring 框架中,以便在许多Spring 项目 和应用场景中更广泛地使用。下面的列表简要地描述了一些可用的消息传递抽象:
- Message: 消息的简单表示,包括头信息和有效载荷。
- MessageHandler: 处理一个消息的合同
- MessageChannel: 发送消息的合同,使生产者和消费者之间的松散耦合。
- SubscribableChannel: 带有 MessageHandler 订阅者的 MessageChannel。
- ExecutorSubscribableChannel: 使用执行者传递消息的 SubscribableChannel。
Java 配置(即 @EnableWebSocketMessageBroker
)和 XML 命名空间配置(即 <websocket:message-broker>
)都使用前面的组件来组装一个消息工作流。下图显示了启用简单内置消息代理时使用的组件:
前面的图显示了三个消息通道(channel):
clientInboundChannel
:用于传递从 WebSocket 客户端收到的消息。clientOutboundChannel
:用于向 WebSocket 客户端发送服务器消息。brokerChannel
:用于从服务器端的应用程序代码中向消息代理发送消息。
下图显示了当外部代理(如 RabbitMQ)被配置为管理订阅和广播消息时使用的组件。
前面两张图的主要区别是使用 「代理中继」(右下角黄色的哪一个组件),通过 TCP 将消息向上传递给外部 STOMP 代理,并从代理向下传递消息给订阅的客户端。
当收到来自 WebSocket 连接的消息时,它们被解码为 STOMP 帧,变成 Spring 消息表示,并被发送到 clientInboundChannel 进行进一步处理。例如,目的地标题以 /app
开头的 STOMP 消息可以被路由到被 @MessageMapping
注解控制器中的方法,而 /topic
和 /queue
消息可以直接被路由到消息代理。
一个处理来自客户端的 STOMP 消息的注解的 @Controller
可以通过 brokerChannel 向消息代理发送消息,而代理则通过clientOutboundChannel 将消息广播给匹配的订阅者。同样的控制器也可以在响应 HTTP 请求时做同样的事情,所以客户端可以执行 HTTP POST,然后一个 @PostMapping
方法可以发送消息给消息代理,以广播给订阅的客户端。
我们可以通过一个简单的例子来追踪这个流程。考虑一下下面的例子,它设置了一个服务器:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
@Controller
public class GreetingController {
@MessageMapping("/greeting")
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
前面的例子支持以下流程:
客户端连接到 http://localhost:8080/portfolio,一旦建立 WebSocket 连接,STOMP 帧就开始在其上流动。
客户端发送一个 SUBSCRIBE 帧,目标头为
/topic/greeting
。一旦收到并解码,该消息将被发送到clientInboundChannel
,然后被路由到消息代理,该代理存储客户的订阅。客户端向
/app/greeting
发送一个 SEND 帧。/app
前缀有助于将其路由到有注解的控制器。在/app
前缀被剥离后,剩余的/greeting
部分的目的地被映射到 GreetingController 的@MessageMapping
方法。从 GreetingController 返回的值被转化为 Spring 消息,其有效载荷基于返回值和默认的目的地头
/topic/greeting
(从输入目的地派生出来的,/app
被/topic
替代)。产生的消息被发送到brokerChannel
并由消息代理处理。消息代理找到所有匹配的订阅者,并通过
clientOutboundChannel
向每个订阅者发送一个 MESSAGE 帧,从那里消息被编码为 STOMP 帧并在 WebSocket 连接上发送。
下一节将提供关于注解方法的更多细节,包括支持的参数和返回值的种类。
一个例子
接 前面的例子,这里来演示下上面描述的流程,关于 stomp js 的 api 可以参考它的文档。
由于前面的例子服务端已经搭建好了,只需要增加一个 controller 就可以了
package cn.mrcode.study.springdocsread.websocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
/**
* @author mrcode
*/
@Controller
public class StompController {
// 接收 send 消息:通过 /app/greeting 发送的消息
@MessageMapping("/greeting")
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
private String getTimestamp() {
return System.currentTimeMillis() + "";
}
}
html 订阅消息,并且发送消息给服务端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>STOMP</title>
</head>
<body>
</body>
<script type="text/javascript" src="/node_modules/webstomp-client/dist/webstomp.min.js"></script>
<script type="text/javascript"
src="https://cdnjs.loli.net/ajax/libs/sockjs-client/1.6.0/sockjs.js"></script>
<script>
// 这里使用 sockJs 库链接
var socket = new SockJS("http://localhost:8080/portfolio");
// 文章上说可以使用 WebSocket 链接。 实际上我这里测试不可以,会报错
// var socket = new WebSocket("ws://localhost:8080/portfolio");
var stompClient = webstomp.over(socket);
// 链接上 服务器时
stompClient.connect({}, function (frame) {
console.log(frame)
// 订阅消息
stompClient.subscribe("/topic/greeting", msg => {
console.log("收到消息:" + msg)
})
// 链接上服务器时,像服务器发送一个消息
stompClient.send("/app/greeting", "我的第一个消息")
})
</script>
</html>
访问页面后,控制台的信息如下
订阅接收到的消息对象如下图所示