Spring国际认证指南:使用 WebSocket 构建交互式 Web 应用程序

 2023-01-26
原文作者:IT胶囊 原文地址:https://juejin.cn/post/7084168225600045092

原标题:Spring国际认证指南|了解如何通过 WebSocket 在浏览器和服务器之间发送和接收消息

202301012031248171.png

本指南将引导您完成创建“Hello, world”应用程序的过程,该应用程序在浏览器和服务器之间来回发送消息。WebSocket 是 TCP 之上的一个轻量级的薄层。这使得它适合使用“子协议”来嵌入消息。在本指南中,我们使用带有 Spring 的STOMP消息传递来创建交互式 Web 应用程序。STOMP 是在较低级别的 WebSocket 之上运行的子协议。

你将建造什么

您将构建一个接受带有用户名的消息的服务器。作为响应,服务器会将问候推送到客户端订阅的队列中。

你需要什么

  • 约15分钟
  • 最喜欢的文本编辑器或 IDE
  • JDK 1.8或更高版本
  • Gradle 4+或Maven 3.2+
  • 您还可以将代码直接导入 IDE:
  • 弹簧工具套件 (STS)
  • IntelliJ IDEA

如何完成本指南

像大多数 Spring入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。

从头开始 ,请继续从 Spring Initializr 开始。

跳过基础知识 ,请执行以下操作:

  • 下载并解压缩本指南的源存储库,或使用Git克隆它:git clone github.com/spring-guid…
  • 光盘进入gs-messaging-stomp-websocket/initial
  • 继续创建资源表示类。

完成后 ,您可以对照中的代码检查结果
gs-messaging-stomp-websocket/complete。

从 Spring Initializr 开始

您可以使用这个预先初始化的项目并单击 Generate 下载 ZIP 文件。此项目配置为适合本教程中的示例。

手动初始化项目:

  1. 导航到start.spring.io。该服务提取应用程序所需的所有依赖项,并为您完成大部分设置。
  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假定您选择了 Java。
  3. 单击 Dependencies 并选择 Websocket
  4. 单击 生成
  5. 下载生成的 ZIP 文件,该文件是根据您的选择配置的 Web 应用程序的存档。

如果您的 IDE 具有 Spring Initializr 集成,您可以从您的 IDE 完成此过程。

你也可以从 Github 上 fork 项目并在你的 IDE 或其他编辑器中打开它。

添加依赖项

在这种情况下,Spring Initializr 没有提供您需要的一切。对于 Maven,您需要添加以下依赖项:

    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>webjars-locator-core</artifactId>
    </dependency>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>sockjs-client</artifactId>
      <version>1.0.2</version>
    </dependency>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>stomp-websocket</artifactId>
      <version>2.3.3</version>
    </dependency>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>bootstrap</artifactId>
      <version>3.3.7</version>
    </dependency>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>jquery</artifactId>
      <version>3.1.1-1</version>
    </dependency>复制

以下清单显示了完成的pom.xml文件:

    <?xml 版本="1.0" 编码="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<父>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<版本>2.6.3</版本>
    		<relativePath/> <!-- 从存储库中查找父级 -->
    	</父>
    	<groupId>com.example</groupId>
    	<artifactId>messaging-stomp-websocket-complete</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>messaging-stomp-websocket-complete</name>
    	<description>Spring Boot 的演示项目</description>
    	<属性>
    		<java.version>1.8</java.version>
    	</属性>
    	<依赖项>
    		<依赖>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-websocket</artifactId>
    		</依赖>
    		<依赖>
    			<groupId>org.webjars</groupId>
    			<artifactId>webjars-locator-core</artifactId>
    		</依赖>
    		<依赖>
    			<groupId>org.webjars</groupId>
    			<artifactId>sockjs-client</artifactId>
    			<版本>1.0.2</版本>
    		</依赖>
    		<依赖>
    			<groupId>org.webjars</groupId>
    			<artifactId>stomp-websocket</artifactId>
    			<版本>2.3.3</版本>
    		</依赖>
    		<依赖>
    			<groupId>org.webjars</groupId>
    			<artifactId>引导</artifactId>
    			<版本>3.3.7</版本>
    		</依赖>
    		<依赖>
    			<groupId>org.webjars</groupId>
    			<artifactId>jquery</artifactId>
    			<版本>3.1.1-1</版本>
    		</依赖>
    
    		<依赖>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>测试</scope>
    		</依赖>
    	</依赖>
    
    	<构建>
    		<插件>
    			<插件>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</插件>
    		</插件>
    	</build>
    
    </项目>

如果使用 Gradle,则需要添加以下依赖项:

    implementation 'org.webjars:webjars-locator-core'
    implementation 'org.webjars:sockjs-client:1.0.2'
    implementation 'org.webjars:stomp-websocket:2.3.3'
    implementation 'org.webjars:bootstrap:3.3.7'
    implementation 'org.webjars:jquery:3.1.1-1'复制

以下清单显示了完成的build.gradle文件:

    插件{
    	id 'org.springframework.boot' 版本 '2.6.3'
    	id 'io.spring.dependency-management' 版本 '1.0.11.RELEASE'
    	标识“java”
    }
    
    组 = 'com.example'
    版本 = '0.0.1-SNAPSHOT'
    源兼容性 = '1.8'
    
    存储库{
    	mavenCentral()
    }
    
    依赖{
    	实现 'org.springframework.boot:spring-boot-starter-websocket'
    	实施 'org.webjars:webjars-locator-core'
    	实施 'org.webjars:sockjs-client:1.0.2'
    	实现 'org.webjars:stomp-websocket:2.3.3'
    	实施 'org.webjars:bootstrap:3.3.7'
    	实施 'org.webjars:jquery:3.1.1-1'
    	testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    测试 {
    	使用JUnitPlatform()
    }

创建资源表示类

现在您已经设置了项目和构建系统,您可以创建您的 STOMP 消息服务。

从考虑服务交互开始这个过程。

该服务将接受在正文为 JSON 对象的 STOMP 消息中包含名称的消息。如果名称为Fred,则消息可能类似于以下内容:

    {
        "name": "Fred"
    }复制

要对带有名称的消息进行建模,您可以创建一个带有name属性和相应getName()方法的普通旧 Java 对象,如以下清单(来自
src/main/java/com/example/messagingstompwebsocket/HelloMessage.java)所示:

    package com.example.messagingstompwebsocket;
    
    public class HelloMessage {
    
      private String name;
    
      public HelloMessage() {
      }
    
      public HelloMessage(String name) {
        this.name = name;
      }
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    }复制

收到消息并提取名称后,服务将通过创建问候语并将该问候语发布到客户端订阅的单独队列中来处理它。问候语也将是一个 JSON 对象,如以下清单所示:

    {
        "content": "Hello, Fred!"
    }复制

要对问候表示进行建模,请添加另一个带有content属性和相应getContent()方法的普通 Java 对象,如以下清单(来自
src/main/java/com/example/messagingstompwebsocket/Greeting.java)所示:

    package com.example.messagingstompwebsocket;
    
    public class Greeting {
    
      private String content;
    
      public Greeting() {
      }
    
      public Greeting(String content) {
        this.content = content;
      }
    
      public String getContent() {
        return content;
      }
    
    }复制

Spring 将使用Jackson JSON库将类型的实例自动编组Greeting为 JSON。

接下来,您将创建一个控制器来接收问候消息并发送问候消息。

创建消息处理控制器

在 Spring 处理 STOMP 消息传递的方法中,STOMP 消息可以路由到@Controller类。例如,GreetingController(from
src/main/java/com/example/messagingstompwebsocket/GreetingController.java) 被映射为处理发送到/hello目的地的消息,如以下清单所示:

    package com.example.messagingstompwebsocket;
    
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.util.HtmlUtils;
    
    @Controller
    public class GreetingController {
    
    
      @MessageMapping("/hello")
      @SendTo("/topic/greetings")
      public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // simulated delay
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
      }
    
    }复制

这个控制器简洁明了,但有很多事情要做。我们一步一步分解。

@MessageMapping注释确保,如果将消息发送到目的地/hello,greeting()则调用该方法。

消息的有效负载绑定到一个HelloMessage对象,该对象被传递到greeting().

在内部,该方法的实现通过使线程休眠一秒钟来模拟处理延迟。这是为了证明,客户端发送消息后,服务器可以根据需要异步处理消息。客户端可以继续它需要做的任何工作,而无需等待响应。

延迟一秒后,该greeting()方法创建一个Greeting对象并返回它。如注解/topic/greetings中所指定,返回值将广播给 的所有订阅者。@SendTo请注意,输入消息中的名称已被清理,因为在这种情况下,它将被回显并在客户端的浏览器 DOM 中重新呈现。

为 STOMP 消息配置 Spring

现在已经创建了服务的基本组件,您可以配置 Spring 以启用 WebSocket 和 STOMP 消息传递。

WebSocketConfig创建一个类似于以下清单的 Java 类(来自
src/main/java/com/example/messagingstompwebsocket/WebSocketConfig.java):

    package com.example.messagingstompwebsocket;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
    
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
      @Override
      public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
      }
    
      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
      }
    
    }复制

WebSocketConfig带有注解,@Configuration表示它是一个 Spring 配置类。它也用 注释@
EnableWebSocketMessageBroker。顾名思义,它@
EnableWebSocketMessageBroker支持由消息代理支持的 WebSocket 消息处理。

该configureMessageBroker()方法实现了
WebSocketMessageBrokerConfigurer配置消息代理的默认方法。它首先调用enableSimpleBroker()以启用一个简单的基于内存的消息代理,以将问候消息传送回带有前缀的目的地的客户端/topic。它还指定/app绑定到带有注释的方法的消息的前缀@MessageMapping。此前缀将用于定义所有消息映射。例如,/app/hello是
GreetingController.greeting()方法映射到处理的端点。

该registerStompEndpoints()方法注册/gs-guide-websocket端点,启用 SockJS 回退选项,以便在 WebSocket 不可用时可以使用备用传输。SockJS 客户端将尝试连接/gs-guide-websocket并使用最佳可用传输(websocket、xhr-streaming、xhr-polling 等)。

创建浏览器客户端

有了服务器端部分,您可以将注意力转移到 JavaScript 客户端,该客户端将向服务器端发送消息并从服务器端接收消息。

创建一个index.html类似于以下清单的文件(来自
src/main/resources/static/index.html):

    <!DOCTYPE html>
    <html>
    <head>
        <title>Hello WebSocket</title>
        <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
        <link href="/main.css" rel="stylesheet">
        <script src="/webjars/jquery/jquery.min.js"></script>
        <script src="/webjars/sockjs-client/sockjs.min.js"></script>
        <script src="/webjars/stomp-websocket/stomp.min.js"></script>
        <script src="/app.js"></script>
    </head>
    <body>
    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
        enabled. Please enable
        Javascript and reload this page!</h2></noscript>
    <div id="main-content" class="container">
        <div class="row">
            <div class="col-md-6">
                <form class="form-inline">
                    <div class="form-group">
                        <label for="connect">WebSocket connection:</label>
                        <button id="connect" class="btn btn-default" type="submit">Connect</button>
                        <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                        </button>
                    </div>
                </form>
            </div>
            <div class="col-md-6">
                <form class="form-inline">
                    <div class="form-group">
                        <label for="name">What is your name?</label>
                        <input type="text" id="name" class="form-control" placeholder="Your name here...">
                    </div>
                    <button id="send" class="btn btn-default" type="submit">Send</button>
                </form>
            </div>
        </div>
        <div class="row">
            <div class="col-md-12">
                | Greetings|
| :-----: | 

            </div>
        </div>
    </div>
    </body>
    </html>复制

这个 HTML 文件导入SockJS和STOMPjavascript 库,这些库将用于通过 STOMP over websocket 与我们的服务器通信。我们还 import app.js,其中包含我们客户端应用程序的逻辑。以下清单(来自
src/main/resources/static/app.js)显示了该文件:

    var stompClient = null;
    
    function setConnected(connected) {
        $("#connect").prop("disabled", connected);
        $("#disconnect").prop("disabled", !connected);
        if (connected) {
            $("#conversation").show();
        }
        else {
            $("#conversation").hide();
        }
        $("#greetings").html("");
    }
    
    function connect() {
        var socket = new SockJS('/gs-guide-websocket');
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function (frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            stompClient.subscribe('/topic/greetings', function (greeting) {
                showGreeting(JSON.parse(greeting.body).content);
            });
        });
    }
    
    function disconnect() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }
    
    function sendName() {
        stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
    }
    
    function showGreeting(message) {
        $("#greetings").append("<tr><td>" + message + "</td></tr>");
    }
    
    $(function () {
        $("form").on('submit', function (e) {
            e.preventDefault();
        });
        $( "#connect" ).click(function() { connect(); });
        $( "#disconnect" ).click(function() { disconnect(); });
        $( "#send" ).click(function() { sendName(); });
    });复制

这个 JavaScript 文件的主要部分是connect()和sendName()函数。

该connect()函数使用SockJS和stomp.js打开到 的连接/gs-guide-websocket,这是我们的 SockJS 服务器等待连接的地方。成功连接后,客户端订阅/topic/greetings目的地,服务器将在该目的地发布问候消息。当在该目的地收到问候时,它会将段落元素附加到 DOM 以显示问候消息。

该sendName()函数检索用户输入的名称并使用 STOMP 客户端将其发送到/app/hello目的地(
GreetingController.greeting()将在哪里接收它)。

如果main.css你愿意,可以省略,或者你可以创建一个空的,这样就可以解决了。

使应用程序可执行

Spring Boot 为您创建了一个应用程序类。在这种情况下,它不需要进一步修改。您可以使用它来运行此应用程序。以下清单(来自
src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java)显示了应用程序类:

    package com.example.messagingstompwebsocket;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class MessagingStompWebsocketApplication {
    
      public static void main(String[] args) {
        SpringApplication.run(MessagingStompWebsocketApplication.class, args);
      }
    }复制

@SpringBootApplication是一个方便的注释,它添加了以下所有内容:

  • @Configuration: 将类标记为应用程序上下文的 bean 定义源。
  • @EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果spring-webmvc位于类路径上,则此注释将应用程序标记为 Web 应用程序并激活关键行为,例如设置DispatcherServlet.
  • @ComponentScan: 告诉 Spring 在包中查找其他组件、配置和服务com/example,让它找到控制器。

该main()方法使用 Spring Boot 的SpringApplication.run()方法来启动应用程序。您是否注意到没有一行 XML?也没有web.xml文件。这个 Web 应用程序是 100% 纯 Java,您不必处理任何管道或基础设施的配置。

构建一个可执行的 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件并运行它。构建可执行 jar 可以在整个开发生命周期、跨不同环境等中轻松地将服务作为应用程序交付、版本化和部署。

如果您使用 Gradle,则可以使用./gradlew bootRun. 或者,您可以使用构建 JAR 文件./gradlew build,然后运行 JAR 文件,如下所示:

    java -jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

如果您使用 Maven,则可以使用./mvnw spring-boot:run. 或者,您可以使用构建 JAR 文件,./mvnw clean package然后运行该 JAR 文件,如下所示:

    java -jar 目标/gs-messaging-stomp-websocket-0.1.0.jar

此处描述的步骤创建了一个可运行的 JAR。您还可以构建经典的 WAR 文件。

显示记录输出。该服务应在几秒钟内启动并运行。

测试服务

现在服务正在运行,将浏览器指向http://localhost:8080并单击“**连接**”按钮。

打开连接后,系统会要求您输入姓名。输入您的姓名,然后单击 发送 。您的姓名将作为 JSON 消息通过 STOMP 发送到服务器。经过一秒钟的模拟延迟后,服务器会发回一条消息,其中包含页面上显示的“Hello”问候语。此时,您可以发送另一个名称,也可以单击“ 断开连接 ”按钮关闭连接。

概括

恭喜!您刚刚使用 Spring 开发了一个基于 STOMP 的消息传递服务。