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(&quot;Disconnected&quot;); } function sendMessage() { var from = document.getElementById('from').value; var text = document.getElementById('text').value; stompClient.send(&quot;/app/chat&quot;, {}, 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 + &quot;: &quot; + messageOutput.text + &quot; (&quot; + messageOutput.time + &quot;)&quot;)); response.appendChild(p); } &lt;/script&gt;
</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