天天看點

Spring國際認證指南:使用 WebSocket 建構互動式 Web 應用程式

原标題:Spring國際認證指南|了解如何通過 WebSocket 在浏覽器和伺服器之間發送和接收消息
Spring國際認證指南:使用 WebSocket 建構互動式 Web 應用程式

本指南将引導您完成建立“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 https://github.com/spring-guides/gs-messaging-stomp-websocket.git
  • CD光牒進入gs-messaging-stomp-websocket/initial
  • 繼續建立資源表示類。

完成後,您可以對照中的代碼檢查結果gs-messaging-stomp-websocket/complete。

從 Spring Initializr 開始

您可以使用這個預先初始化的項目并單擊 Generate 下載下傳 ZIP 檔案。此項目配置為适合本教程中的示例。

手動初始化項目:

  1. 導航到https://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(fromsrc/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">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </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你願意,可以省略,或者你可以建立一個空的,這樣<link>就可以解決了。

使應用程式可執行

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 檔案。

顯示記錄輸出。該服務應在幾秒鐘内啟動并運作。

測試服務

概括