1、概览 {#1概览}
本文将带你学习如何使用 Spring 4 中引入的 WebSocket 功能来实现一个简单的聊天应用。
WebSockets 是 Web 浏览器和服务器之间的一种双向、全双工、持久连接。一旦建立了 WebSocket 连接,该连接就会一直打开,直到客户端或服务器关闭该连接。
2、Maven 依赖 {#2maven-依赖}
在 pom.xml
中添加所需的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
此外,还需要添加 Jackson 依赖,用于序列化/反序列化 JSON 格式的消息。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
3、启用 WebSocket {#3启用-websocket}
首先,在配置类上通过 @EnableWebSocketMessageBroker
注解来启用 WebSocket
功能。
配置类需要继承
AbstractWebSocketMessageBrokerConfigurer
。
顾名思义,它能在 Message Broker 的支持下处理 WebSocket 消息:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
如上,通过 configureMessageBroker
方法配置 Message Broker。
首先,启用了一个基于内存的 Message Broker,用于将消息返回给以 /topic
为前缀的客户端目标。
设置应用目标(通过 @MessageMapping
注解的方法)的前缀为 /app
。
registerStompEndpoints
方法注册了/chat
端点,从而启用了 Spring 的 STOMP 支持。这里为了提高灵活性,还添加了 SockJS 的支持,但是本文不会使用。
该端点(前缀为 /app
时)是 ChatController.send()
方法映射处理的端点。
启用 SockJS
回退选项,以便在无法使用 WebSockets 时使用其他消息传递选项。这一点非常有用,因为 WebSocket 在某些浏览器中尚未得到支持,并且可能受到限制性网络代理的阻止。
当需要时,回退功能允许应用在运行时优雅地降级到非 WebSocket 的替代方案,同时仍然可以使用 WebSocket API。
4、创建 Message Model {#4创建-message-model}
配置好 WebSocket 后,创建要发送的消息。
端点接受包含发送人和文本的 STOMP 消息,该消息是一个 JSON
对象。
{
"from": "John",
"text": "Hello!"
}
其对应的 Model 类如下:
public class Message {
private String from;
private String text;
// get、set 省略
}
默认情况下,Spring 使用 Jackson 将 Model 对象转换为 JSON 或从 JSON 转换为 Model 对象。
5、创建消息处理 Controller {#5创建消息处理-controller}
如你所见,Spring 处理 STOMP 消息的方法是将 Controller 方法与配置的端点关联起来。
可以通过 @MessageMapping
注解来做到这一点。
@MessageMapping("/chat")
@SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
String time = new SimpleDateFormat("HH:mm").format(new Date());
return new OutputMessage(message.getFrom(), message.getText(), time);
}
在本例中,创建了另一个名为 OutputMessage
的 Model 对象来表示发送到配置目的地的输出消息,并在对象中填充发件人(sender)和从传入消息中提取的消息内容,以及一个时间戳。
处理完消息后,会将其发送到使用 @SendTo
注解定义的适当目的地。/topic/messages
目标的所有订阅者都将收到该消息。
6、创建浏览器客户端 {#6创建浏览器客户端}
服务器配置完毕后,使用 sockjs-client 库构建一个与消息系统交互的简单 HTML 页面。
首先,需要导入 sockjs
和 stomp
JavaScript 客户端库。
接下来,创建一个 connect()
函数来打开与端点的通信,创建一个 sendMessage()
函数来发送 STOMP 消息,创建一个 disconnect()
函数来关闭通信:
<html>
<head>
<title>Chat WebSocket</title>
<script src="resources/js/sockjs-0.3.4.js"></script>
<script src="resources/js/stomp.js"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility
= connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/messages', function(messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
}
function disconnect() {
if(stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendMessage() {
var from = document.getElementById('from').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chat", {},
JSON.stringify({'from':from, 'text':text}));
}
function showMessageOutput(messageOutput) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(messageOutput.from + ": "
+ messageOutput.text + " (" + messageOutput.time + ")"));
response.appendChild(p);
}
</script>
</head>
<body onload="disconnect()">
<div>
<div>
<input type="text" id="from" placeholder="Choose a nickname"/>
</div>
<br />
<div>
<button id="connect" onclick="connect();">Connect</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">
Disconnect
</button>
</div>
<br />
<div id="conversationDiv">
<input type="text" id="text" placeholder="Write a message..."/>
<button id="sendMessage" onclick="sendMessage();">Send</button>
<p id="response"></p>
</div>
</div>
</body>
</html>
7、测试 {#7测试}
为了测试,可以多打开几个浏览器窗口并访问聊天页面:
http://localhost:8080
输入昵称并点击连接按钮后,就可以加入聊天了。此时撰写并发送一条消息,就可以在所有已加入聊天的浏览器会话中看到它。
效果如下:
8、总结 {#8总结}
本文介绍了 Spring 的 WebSocket 支持,以及如何使用 sockjs 和 stomp JavaScript 库构建了一个简单的客户端应用。
Ref:https://www.baeldung.com/websockets-spring