Spring官方教程:使用WebSocket構建交互web應用

本教程將引導你構建一個可以在瀏覽器及服務器端發送和接收消息的“hello world”程序。WebSocket是構建在TCP協議上的非常輕量級的一層。它非常適合使用“子協議”來嵌入消息。在本嚮導中,我們將深入研究使用“STOMP (面向流的文本消息協議)”消息機制,結合Spring來創建一個交互Web應用。

你將要構建的

你將要構建一個接受包含了用戶名的消息的服務器。作爲響應,服務器會在隊列中推送一句問候語給對應的客戶端。

你需要準備的

  • 大約15分鐘
  • 一個趁手的文本編輯器或IDE
  • JDK 1.8或更高版本
  • Gradle 2.3+ 或 Maven 3.0+
  • 你可以從本教程上,或Spring Tool Suite (STS) 的網頁上導入代碼。

如何完成本教程

就像大部分Spring入門指南 一樣,你可以一步步跟隨教程去做,或者跳過基礎的你已經熟悉的那些部分。不論怎樣,你都能得到可以運行的代碼。

要跳過基礎部分,做如下步驟:

  • git clone https://github.com/spring-guides/gs-messaging-stomp-websocket.git
  • cd進入 gs-messaging-stomp-websocket/initial

(此處有部分省略,只保留使用Maven配置的方式)

使用Maven構建

首先你應該建立一個基本的構建腳本。當你使用Spring的時候,你可以使用任何你喜歡的構建系統,但使用Maven來構建的代碼在這裏給出了。如果你不熟悉Maven的話,參考這篇使用Maven構建Java項目。

創建目錄結構

在你準備開工的項目目錄內,創建如下的目錄結構。例如,在*nix系統下,輸入如下命令來創建目錄結構:

mkdir -p src/main/java/hello

└── src
    └── main
        └── java
            └── hello

pom.xml

<?xml version="1.0" encoding="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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-messaging-stomp-websocket</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.6.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot Maven 插件 提供了許多方便的功能:

  • 它集合了所有的classpath中提及的jar包,並構建出一個獨立的可執行jar包,這使得傳輸和執行你的jar包更加簡單。
  • 它會查找 public static void main() 方法,並將包含main方法的這個類標記爲主類。
  • 它提供了一個內建的依賴處理器,處理器通過設定版本號來匹配Spring Boot 依賴。你可以任意重寫版本號,但默認Boot會自動選擇版本號。

創建一個資源描述類

現在,你就可以設置工程及編譯系統,來創建你的STOMP消息服務了。我們將從考慮服務交互開始。
服務器接收一個包含了名字信息的JSON 對象作爲STOMP消息。例如名字叫做“Fred”,那麼這個消息看起來就像下邊給出的這樣:

{
    "name": "Fred"
}

爲了給這個包含了名字的消息建模,你可以創建一個具有name屬性的普通java對象,並給它添加一個getName() 方法:

src/main/java/hello/HelloMessage.java

package hello;
public class HelloMessage {
    private String name;
    public String getName() {
        return name;
    }
}

在接收到消息並讀取出名字之後,服務器將根據名字創建出一句問候語,並將它發送給對應的客戶端的隊列。這句問候語同樣是以JSON對象形式發送的,就像下面這樣:

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

同樣,我們給問候語建模:
src/main/java/hello/Greeting.java

package hello;
public class Greeting {
    private String content;
    public Greeting(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    } 
}

Spring會使用Jackson JSON 庫自動將這個Greeting類的對象序列化爲JSON對象。
接下來,你將創建一個用來接收hello消息併發送問候消息的控制器。

創建一個消息處理控制器

在Spring中使用STOMP消息的模式內,STOMP消息可以使用被@Controller註解的java類來分發。例如GreetingController被映射用來處理來自”/hellp”路徑的消息。

src/main/java/hello/GreetingController.java

package hello;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(3000); // simulated delay
        return new Greeting("Hello, " + message.getName() + "!");
    }
}

這個控制器比較簡潔,但實際上做了很多東西。我們逐步來分析。

  • @MessageMapping註解用來聲明如果消息發往路徑”/hello”,greeting() 方法會被調用。
  • 發送來的消息被綁定爲一個HelloMessage 類的對象,並傳入greeting()方法。
    在這個方法的內部,我們調用線程的休眠方法模擬了三秒的處理延遲。這是用來演示當客戶端發送一個消息進來後,服務器端可以異步地處理這些消息。客戶端可以繼續它的工作,而不必等待服務器的響應。
  • 在三秒的延遲後,greeting() 方法創建了一個Greeting對象,並且將其返回。返回值被廣播到所有通過@SendTo 註解訂閱了”/topic/greetings”路徑下的訂閱者。

爲STOMP消息收發指定Spring

那麼現在,所有的服務器需要的必要組件就粗昂建好了,你可以指定Spring來啓用WebSocket和STOMP消息收發功能。
創建一個叫做WebSocketConfig的Java類,就像這樣:

src/main/java/hello/WebSocketConfig.java

package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@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("/hello").withSockJS();
    }
}

WebSocketConfig由@Configuration 註解,從而被指定爲Spring的配置類。這個類同時被@EnableWebSocketMessageBroker.註解。就像這個註解名字的本意,@EnableWebSocketMessageBroker 註解啓用了WebSocket消息處理功能——由一個消息代理在後臺處理。

configureMessageBroker()方法重寫自WebSocketMessageBrokerConfigurer 類的默認方法,它是用來定義消息代理者的。它在調用enableSimpleBroker() 方法後被啓動,並啓用一個簡單的基於內存的消息代理從而攜帶問候消息返回給訪問了路徑前綴是”/topic”的那個客戶端。它還給消息指定了由@MessageMapping註解的方法定義的”/app”作爲前綴。

registerStompEndpoints()方法註冊了”/hello”端點,使得SockJS的替補選項被啓用,這時當WebSocket不被瀏覽器支持的時候,客戶端可以使用其他可供替代的消息選項。這樣,當一個端點的前綴是”/app”的時候,它將會映射給GreetingController.greeting()方法,並由它處理。

創建一個瀏覽器客戶端

我們已經建立好了服務器端,現在讓我們把精力放在將要給服務端發送消息並接收消息的JavaScript客戶端。
創建一個index.html文件,就像這樣:
src/main/resources/static/index.html

<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<script src="sockjs-0.3.4.js"></script>
<script src="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('/hello');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function(frame) {
        setConnected(true);
        console.log('連接了: ' + frame);
        stompClient.subscribe('/topic/greetings', function(greeting){
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

function disconnect() {
    if (stompClient != null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("已斷開連接");
}

function sendName() {
    var name = document.getElementById('name').value;
    stompClient.send("/app/hello", {}, JSON.stringify({ 'name': name }));
}

function showGreeting(message) {
    var response = document.getElementById('response');
    var p = document.createElement('p');
    p.style.wordWrap = 'break-word';
    p.appendChild(document.createTextNode(message));
    response.appendChild(p);
}
</script>
</head>
<body onload="disconnect()">
    <noscript><h2 style="color: #ff0000">貌似你的瀏覽器不支持JavaScript!Websocket依賴於Javascript。請啓用
Javascript並刷新頁面!</h2></noscript>
    <div>
        <div>
            <button id="connect" onclick="connect();">連接</button>
            <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接</button>
        </div>
        <div id="conversationDiv">
            <label>你叫啥?</label><input type="text" id="name" />
            <button id="sendName" onclick="sendName();">發送</button>
            <p id="response"></p>
        </div>
    </div>
</body>
</html>

我們重點關注HTML文件中connect()和 sendName()兩個JavaScript方法的代碼。

  • connect() 方法使用了SockJS和 stomp.js打開了一個向”/gs-messaging-stomp-websocket/hello”的連接,這個連接正是由GreetingController 類處理的。在成功建立連接後,它指定了”/topic/greetings”路徑,在這裏服務器將會返回問候語消息。當問候語被目標收到後,瀏覽器將在DOM結構中插入一個段落節點,從而顯示出問候語。
  • sendName() 方法則是獲取用戶輸入的名字,並使用STOMP客戶端將它發送給”/app/hello”路徑 (GreetingController.greeting() 方法將會接收到它)。

創建可執行應用

雖然我們可以將整個服務端打包爲一個傳統的WAR包並在服務器上運行,但更簡單直接的演示方式是創建一個獨立的應用。將所有的代碼打包爲一個獨立的可執行JAR包,並由傳統的Java的main() 方法驅動執行。這樣做的話,你可以使用Spring內建支持的嵌入式Tomcat servlet容器作爲HTTP運行環境,而不是部署一個外部實例。

src/main/java/hello/Application.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@SpringBootApplication是一個很方便的註解,它包含了如下功能:

  • @Configuration註明使用當前類作爲application context的bean定義的來源。
  • @EnableAutoConfiguration告訴Spring Boot開始將由classpath定義的、其他bean定義的以及各種配置信息定義的bean添加進來。
    一般情況下,你需要爲Spring MVC應用添加@EnableWebMvc註解,但當Spring Boot在classpath中找到spring-webmvc字樣時,它會自動添加這個註解。這標明瞭本應用是一個web應用,並且啓動了一些關鍵行爲,比如設置一個DispatcherServlet。
  • @ComponentScan告訴Spring在hello 包內查找其他的組件、配置信息及服務,從而使得HelloController被找到。
  • main()方法使用Spring Boot的SpringApplication.run()方法啓動了整個實例。你有沒有發現我們沒有使用哪怕一行xml代碼?甚至沒有web.xml文件。這個web應用是由100%的純Java驅動的,並且你不需要去配置任何的底層信息。

創建一個可執行JAR包

(此處略去使用Gradle打包的內容)
如果你使用的是Maven,你可以使用mvn spring-boot:run命令來啓動應用。或者使用mvn clean package命令將其打包成JAR文件。打包完成後可以使用如下命令運行:
java -jar target/gs-messaging-stomp-websocket-0.1.0.jar
默認情況下程序會創建一個可執行JAR包,你也可以通過修改參數創建傳統的WAR包。
日誌信息將會顯示出來。服務器將會在數秒鐘內啓動完畢。

測試服務器

現在服務器端已經跑起來了,在你的瀏覽器內輸入http://localhost:8080 並轉到。
進入網頁之後,輸入用戶名並點擊“發送”按鈕,你的名字就會以JSON消息的方式通過STOMP協議發送到服務器上。經過3秒的模擬延遲,服務器端將返回一個帶有”hello”的問候語,並被瀏覽器顯示到頁面上。之後,你可以發送一個新的名字,或者點擊“斷開連接”按鈕來關閉連接。

小結

恭喜!你已經完成了使用Spring開發一個基於STOMP的消息服務。

原文地址:http://spring.io/guides/gs/messaging-stomp-websocket/
翻譯時略有刪改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章