1. 概述

在这个教程中,我们将创建一个简单的网络应用,利用Spring框架4.0引入的WebSocket功能 实现消息传递。

WebSocket是一种双向、全双工且持久化的浏览器与服务器连接。一旦建立连接,除非客户端或服务器决定关闭,否则连接会一直保持打开状态。

一个典型的应用场景可能是多人之间的实时通信,比如聊天。在我们的示例中,我们将构建一个简单的聊天客户端。

2. Maven依赖

由于这是一个基于Maven的项目,我们首先需要在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构建消息体。

这样可以让Spring将我们的Java对象转换为/从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>

请在Maven中央仓库查找以上库的最新版本。

3. 在Spring中启用WebSocket

首先,我们需要启用WebSocket功能。为此,我们需要在应用程序中添加配置,并使用@EnableWebSocketMessageBroker注解标记这个类。

顾名思义,它支持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方法用于配置消息代理

首先,我们启用内存消息代理,以便在以“/topic”前缀的目的地承载回发给客户端的消息。

通过将“/app”前缀指定为针对@MessageMapping注解的方法(即针对应用程序的方法)的目标,我们完成了简单的配置。

registerStompEndpoints方法注册了“/chat”端点,从而启用Spring的STOMP支持。需要注意的是,我们在这里还添加了一个不依赖于SockJS的端点,以提高弹性。

这个端点,当以“/app”前缀时,是ChatController.send()方法映射到处理的端点。它还启用了SockJS的替代选项,以便在WebSocket不可用时使用其他消息选项。这是有用的,因为并非所有浏览器都支持WebSocket,而且某些严格的网络代理可能会阻止其使用。

这些替代方案允许应用程序使用WebSocket API,但在必要时在运行时优雅地降级到非WebSocket的替代方案。

4. 创建消息模型

现在我们已经设置了项目并配置了WebSocket功能,我们需要创建一个发送的消息。

端点将接受包含发送者名称和文本的STOMP消息,其消息体是一个JSON对象。

消息可能看起来像这样:

{
    "from": "John",
    "text": "Hello!"
}

为了表示携带文本的消息,我们可以创建一个简单的Java对象,具有fromtext属性:

public class Message {

    private String from;
    private String text;

    // getters and setters
}

默认情况下,Spring将使用Jackson库将我们的模型对象转换为和从JSON

5. 创建消息处理控制器

正如我们所见,Spring处理STOMP消息的方式是将控制器方法与配置的端点关联起来。我们可以通过@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的模型对象,用于表示发送到配置目的地的输出消息。我们将从接收到的消息中获取发送者和消息文本,并为其添加一个时间戳。

处理完消息后,我们使用@SendTo注解将其发送到适当的目的地。订阅“*/topic/messages*”目的地的所有用户都将接收到消息。

6. 创建浏览器客户端

在服务器端完成配置后,我们将使用**sockjs-client**库来构建一个简单的HTML页面,与我们的消息系统进行交互。

首先,我们需要导入sockjsstomp的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. 测试示例

要测试我们的示例,可以打开几个浏览器窗口并访问聊天页面:

http://localhost:8080

完成后,输入昵称并点击连接按钮,就可以加入聊天。如果我们编写并发送一条消息,所有已连接到聊天的浏览器会看到这条消息。

查看截图:

websockets-chat

8. 总结

在这篇文章中,我们探讨了Spring的WebSocket支持。我们了解了其服务器端的配置,并使用sockjsstompJavaScript库创建了简单的客户端对应部分。

示例代码可以在GitHub项目中找到。