SpringBoot 2 使用 WebFlux 構建響應式 RESTful Web 服務

開篇詞

該指南將引導你創建 “Hello, Spring!”。帶有 Spring WebFlux(版本 5 的新功能)的 RESTful Web 服務,然後通過 WebClient(版本 5 的新功能)使用該服務。

該指南展示了使用 Spring WebFlux 的功能方法。你可以在 WebFlux 中使用註解

 

你將創建的應用

我們將使用 Spring WebFlux 和該服務的 WebClient 使用者構建 RESTful 網絡服務。我們可以在 System.out 和以下位置看到輸出:

http://localhost:8080/hello

 

你將需要的工具

如何完成這個指南

像大多數的 Spring 入門指南一樣,你可以從頭開始並完成每個步驟,也可以繞過你已經熟悉的基本設置步驟。如論哪種方式,你最終都有可以工作的代碼。

  • 要從頭開始,移步至用 Gradle 來構建
  • 要跳過基礎,執行以下操作:
    • 下載並解壓縮該指南將用到的源代碼,或藉助 Git 來對其進行克隆操作:git clone https://github.com/spring-guides/gs-reactive-rest-service.git
    • 切換至 gs-reactive-rest-service/initial 目錄;
    • 跳轉至該指南的創建 WebFlux 句柄

待一切就緒後,可以檢查一下 gs-reactive-rest-service/complete 目錄中的代碼。
 

用 Gradle 來構建

首先,我們設置一個基本的構建腳本。在使用 Spring 構建應用時可以使用任何喜歡的構建系統,但此處包含使用 GradleMaven 所需的代碼。如果你都不熟悉,請參閱使用 Gradle 構建 Java 項目使用 Maven 構建 Java 項目

創建目錄結構

在我們選擇的項目目錄中,創建以下自目錄結構;例如,在 *nix 系統上使用 mkdir -p src/main/java/hello

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

創建 Gradle 構建文件

以下是初始 Gradle 構建文件。
build.gradle

buildscript {
    ext {
        springBootVersion = '2.2.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

bootJar {
    baseName = 'gs-reactive-rest-service'
    version =  '0.1.0'
}

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-webflux')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('io.projectreactor:reactor-test')
}

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

  • 它收集類路徑上的所有 jar,並構建一個可運行的單個超級 jar,這使執行和傳輸服務更加方便;
  • 它搜索 public static void main() 方法並將其標記爲可運行類;
  • 它提供了一個內置的依賴解析器,用於設置版本號以及匹配 Spring Boot 依賴。我們可以覆蓋所需的任何版本,但默認爲 Boot 選擇的一組版本。
     

用 Maven 來構建

首先,我們搭建一個基本的構建腳本。使用 Spring 構建應用時,可以使用任何喜歡的構建系統,但是此處包含了使用 Maven 所需的代碼。如果你不熟悉 Maven,請參閱使用 Maven 構建 Java 項目

創建目錄結構

在我們選擇的項目目錄中,創建以下自目錄結構;例如,在 *nix 系統上使用 mkdir -p src/main/java/hello

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

創建 Maven 構建文件

以下是初始 Maven 構建文件。
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-reactive-rest-service</artifactId>
    <version>0.1.0</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</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 插件提供了許多方便的功能:

  • 它收集類路徑上的所有 jar,並構建一個可運行的單個超級 jar,這使執行和傳輸服務更加方便;
  • 它搜索 public static void main() 方法並將其標記爲可運行類;
  • 它提供了一個內置的依賴解析器,用於設置版本號以及匹配 Spring Boot 依賴。我們可以覆蓋所需的任何版本,但默認爲 Boot 選擇的一組版本。
     

用 IDE 來構建

創建 WebFlux 句柄

在 Spring Reactive 方法中,我們使用處理程序來處理請求並創建響應,如下示例所示:

src/main/java/hello/GreetingHandler.java

package hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {

  public Mono<ServerResponse> hello(ServerRequest request) {
    return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
      .body(BodyInserters.fromValue("Hello, Spring!"));
  }
}

這個簡單的反應式類始終返回 “Hello, Spring!”。它可能返回許多其他內容,包括來自數據庫的項目流,通過計算生成的項目流,等等。請注意反應式代碼:一個包含 ServerResponse 主體的 Mono 對象。
 

創建路由

在該應用中,我們使用路由來處理我們公開的唯一路由("/hello"),如以下示例所示:

src/main/java/hello/GreetingRouter.java

package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class GreetingRouter {

  @Bean
  public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {

    return RouterFunctions
      .route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
  }
}

路由在 /hello 路徑上偵聽流量,並返回我們的反應式處理程序類提供的值。
 

創建 WebClient

Spring MVC RestTemplate 類本質上是阻塞的。因此,我們不想在反應式應用中使用它。對於反應式應用,Spring 提供了非阻塞的 WebClient 類。我們將使用 WebClient 實現來使用我們的 RESTful 服務:

src/main/java/hello/GreetingWebClient.java

package hello;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import reactor.core.publisher.Mono;

public class GreetingWebClient {
  private WebClient client = WebClient.create("http://localhost:8080");

  private Mono<ClientResponse> result = client.get()
      .uri("/hello")
      .accept(MediaType.TEXT_PLAIN)
      .exchange();

  public String getResult() {
    return ">> result = " + result.flatMap(res -> res.bodyToMono(String.class)).block();
  }
}

WebClient 使用反應式功能,以 Mono 的形式保存我們指定的 URI 的內容,並使用一個函數(在 getResult 方法中)將該內容轉換爲字符串。如果我們有不同的要求,則可以將其轉換爲字符串以外的形式。由於我們要將結果放入 System.out,因此這裏將使用字符串。

WebClient 也可以用於非反應式阻塞服務進行通信。

 

使應用可執行

儘管可以將服務打包爲傳統的 WAR 文件以部署到外部應用服務器,但是下面演示的更簡單的方法創建了一個獨立的應用。我們將所有內容打包在一個可執行的 JAR 文件中,由一個經典的 Java main() 方法驅動。在該過程中,我們使用 Reactive Spring 的支持將 Netty 服務器作爲 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);

    GreetingWebClient gwc = new GreetingWebClient();
    System.out.println(gwc.getResult());
  }
}

@SpringBootApplication 是一個便利的註解,它添加了以下所有內容:

  • @Configuration:將類標註爲應用上下文 Bean 定義的源;
  • @EnableAutoConfiguration:告訴 Spring Boot 根據類路徑配置、其他 bean 以及各種屬性的配置來添加 bean;
  • @ComponentScan:告知 Spring 在 hello 包中尋找他組件、配置以及服務。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法啓動應用。
 

構建可執行 JAR

我們可以結合 Gradle 或 Maven 來從命令行運行該應用。我們還可以構建一個包含所有必須依賴項、類以及資源的可執行 JAR 文件,然後運行該文件。在整個開發生命週期中,跨環境等等情況下,構建可執行 JAR 可以輕鬆地將服務作爲應用進行發佈、版本化以及部署。

如果使用 Gradle,則可以藉助 ./gradlew bootRun 來運行應用。或通過藉助 ./gradlew build 來構建 JAR 文件,然後運行 JAR 文件,如下所示:

java -jar build/libs/gs-reactive-rest-service-0.1.0.jar

如果使用 Maven,則可以藉助 ./mvnw spring-boot:run 來運行該用。或可以藉助 ./mvnw clean package 來構建 JAR 文件,然後運行 JAR 文件,如下所示:

java -jar target/gs-reactive-rest-service-0.1.0.jar

我們還可以將 JAR 應用轉換成 WAR 應用

顯示日誌記錄輸出。該服務應在幾秒內啓動並運行。

服務啓動後,我們會看到以下內容:

>> result = Hello, Spring!

改行來自 WebClient 正在使用的反應式內容。自然,與將輸出放入 System.out 相比,我們會發現輸出更有趣。
 

測試應用

現在該應用正在運行,我們可以對其進行測試。首先,我們可以打開瀏覽器並轉到 http://localhost:8080/hello,然後看到 “Hello, Spring!”。對於該指南,我們還創建了一個測試類,以使我們開始使用 WebTestClient 類進行測試。

src/test/java/hello/GreetingRouterTest.java

package hello;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@ExtendWith(SpringExtension.class)
//  We create a `@SpringBootTest`, starting an actual server on a `RANDOM_PORT`
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingRouterTest {

  // Spring Boot will create a `WebTestClient` for you,
  // already configure and ready to issue requests against "localhost:RANDOM_PORT"
  @Autowired
  private WebTestClient webTestClient;

  @Test
  public void testHello() {
    webTestClient
      // Create a GET request to test an endpoint
      .get().uri("/hello")
      .accept(MediaType.TEXT_PLAIN)
      .exchange()
      // and use the dedicated DSL to test assertions against the response
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, Spring!");
  }
}

 

概述

恭喜你!我們已經開發了一個 Reactive Spring 應用,其中包括一個 WebClient 來使用 RESTful 服務!
 

想看指南的其他內容?請訪問該指南的所屬專欄:《Spring 官方指南

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