Intro to WebSockets with Spring – 使用Spring的WebSockets介绍

最后修改: 2016年 5月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll create a simple web application that implements messaging using the new WebSocket capabilities introduced with Spring Framework 4.0.

在本教程中,我们将创建一个简单的Web应用程序,使用Spring Framework 4.0引入的新WebSocket功能实现消息传递。

WebSockets is a bidirectional, full-duplex, persistent connection between a web browser and a server. Once a WebSocket connection is established, the connection stays open until the client or server decides to close this connection.

WebSockets是网络浏览器和服务器之间的一种双向、全双工、持久的连接。一旦建立了WebSocket连接,该连接将保持开放,直到客户或服务器决定关闭该连接。

A typical use case could be when an app involves multiple users communicating with each other, such as in a chat. We will build a simple chat client in our example.

一个典型的用例可能是当一个应用程序涉及到多个用户相互交流时,例如在聊天中。我们将在示例中构建一个简单的聊天客户端。

2. Maven Dependencies

2.Maven的依赖性

Since this is a Maven-based project, we first add the required dependencies to the pom.xml:

由于这是一个基于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>

In addition, we need to add the Jackson dependencies since we’ll use JSON to build the body of our messages.

此外,我们需要添加Jackson依赖项,因为我们将使用JSON来构建我们的消息正文。

This allows Spring to convert our Java object to/from 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>

Look for the newest version of the libraries above on Maven Central.

Maven Central上查找上述库的最新版本。

3. Enable WebSocket in Spring

3.在Spring中启用WebSocket

First, we enable the WebSocket capabilities. In order to do this, we need to add a configuration to our application and annotate this class with @EnableWebSocketMessageBroker.

首先,我们启用WebSocket功能。为了做到这一点,我们需要向我们的应用程序添加一个配置,并用@EnableWebSocketMessageBroker来注释这个类。

As its name suggests, it enables WebSocket message handling, backed by a 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();
    }
}

Here we can see that the method configureMessageBroker is used to configure the message broker.

在这里,我们可以看到方法configureMessageBroker被用来配置消息代理。

First, we enable an in-memory message broker to carry the messages back to the client on destinations prefixed with “/topic”.

首先,我们启用一个内存中的消息代理,将消息传回客户端,目的地前缀为”/topic”。

We complete our simple configuration by designating the “/app” prefix to filter destinations targeting application annotated methods (via @MessageMapping).

我们完成我们的简单配置,指定”/app “前缀来过滤针对应用程序注释方法的目的地(通过@MessageMapping)。

The registerStompEndpoints method registers the “/chat” endpoint, enabling Spring’s STOMP support. Keep in mind that we are also adding an endpoint here that works without the SockJS for the sake of elasticity.

registerStompEndpoints方法注册了”/chat “端点,启用了Spring的STOMP支持。请记住,我们在这里也添加了一个端点,为了弹性起见,不使用SockJS也可以工作。

This endpoint, when prefixed with “/app”, is the endpoint that the ChatController.send() method is mapped to handle.

这个端点,如果前缀为”/app”,就是ChatController.send()方法被映射为处理的端点。

It also enables the SockJS fallback options so that alternative messaging options may be used if WebSockets are not available. This is useful since WebSocket is not supported in all browsers yet and may be precluded by restrictive network proxies.

它还启用了SockJS回退选项,以便在WebSocket不可用时可以使用替代的消息传递选项。这很有用,因为WebSocket尚未被所有浏览器支持,而且可能被限制性网络代理排除在外。

The fallbacks let the applications use a WebSocket API but gracefully degrade to non-WebSocket alternatives when necessary at runtime.

这些回退让应用程序使用WebSocket API,但在运行时必要时可优雅地降级为非WebSocket替代品。

4. Create the Message Model

4.创建消息模型

Now that we’ve set up the project and configured the WebSocket capabilities, we need to create a message to send.

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

The endpoint will accept messages containing the sender name and a text in a STOMP message whose body is a JSON object.

该端点将接受包含发件人姓名和一个文本的STOMP消息,其主体是一个JSON对象。

The message might look like this:

该信息可能看起来像这样。

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

To model the message carrying the text, we can create a simple Java object with from and text properties:

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

public class Message {

    private String from;
    private String text;

    // getters and setters
}

By default, Spring will use the Jackson library to convert our model object to and from JSON.

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

5. Create a Message-Handling Controller

5.创建一个消息处理控制器

As we’ve seen, Spring’s approach to working with STOMP messaging is to associate a controller method to the configured endpoint. We can do this through the @MessageMapping annotation.

正如我们所看到的,Spring处理STOMP消息传递的方法是将控制器方法与配置的端点相关联。我们可以通过@MessageMapping 注释来实现。

The association between the endpoint and the controller gives us the ability to handle the message if needed:

端点和控制器之间的关联使我们有能力在需要时处理消息。

@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);
}

In our example, we’ll create another model object named OutputMessage to represent the output message sent to the configured destination. We populate our object with the sender and the message text taken from the incoming message and enrich it with a timestamp.

在我们的例子中,我们将创建另一个名为OutputMessage的模型对象来表示发送至配置的目的地的输出消息。我们用发件人和从传入的消息中提取的消息文本来填充我们的对象,并用一个时间戳来丰富它。

After handling our message, we send it to the appropriate destination defined with the @SendTo annotation. All subscribers to the “/topic/messages” destination will receive the message.

在处理完我们的消息后,我们把它发送到用@SendTo注解定义的适当目的地。所有”/topic/messages“目的地的订阅者将收到该消息。

6. Create a Browser Client

6.创建一个浏览器客户端

After making our configurations in the server-side, we’ll use the sockjs-client library to build a simple HTML page that interacts with our messaging system.

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

First, we need to import the sockjs and stomp JavaScript client libraries.

首先,我们需要导入sockjsstomp JavaScript客户端库。

Next, we can create a connect() function to open the communication with our endpoint, a sendMessage() function to send our STOMP message and a disconnect() function to close the communication:

接下来,我们可以创建一个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. Testing the Example

7.测试实例

To test our example, we can open a couple of browser windows and access the chat page:

为了测试我们的例子,我们可以打开几个浏览器窗口,访问聊天页面。

http://localhost:8080

Once this is done, we can join the chat by entering a nickname and hitting the connect button. If we compose and send a message, we can see it in all browser sessions that have joined the chat.

一旦完成,我们可以通过输入一个昵称并点击连接按钮加入聊天。如果我们编写并发送一条消息,我们可以在所有加入聊天的浏览器会话中看到它。

Take a look at the screenshot:

看看屏幕截图:

websockets-chat

8. Conclusion

8.结论

In this article, we explored Spring’s WebSocket support. We’ve seen its server-side configuration and built a simple client-side counterpart using sockjs and stomp JavaScript libraries.

在本文中,我们探究了Spring的WebSocket支持。我们看到了它的服务器端配置,并使用sockjsstompJavaScript库构建了一个简单的客户端对应的

The example code can be found in the GitHub project.

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