天天看點

使用 Spring WebFlux 和 Reactor 的響應式 Java

作者:千鋒IT教育

Spring WebFlux 是 Java 中最流行的反應式程式設計架構之一。以下是結合使用 WebFlux 和 Reactor 的實踐。

使用 Spring WebFlux 和 Reactor 的響應式 Java

響應式程式設計是從函數世界演變而來的一種重要的編碼風格。反應式代碼利用流、生産者和訂閱者的事件驅動原理來簡化複雜的邏輯并支援應用程式中 IO 處理的異步、非阻塞處理。在 Java 中,這意味着我們可以使用java.nio具有表現力 API 的(非阻塞 IO)包來建構應用程式。許多架構和方法都支援 Java 中的反應性。最流行的之一是Spring WebFlux。本文是對使用 Spring WebFlux 進行響應式 Java 程式設計的實踐介紹。

與 Spring 的反應性

反應性為我們提供了一個強大的單一慣用語來描述群組合 Web 請求和資料通路等功能。一般來說,我們使用事件生産者和訂閱者來描述異步事件源以及事件被激活時應該發生的情況。

在典型的 Spring 架構風格中,WebFlux 提供了一個用于建構反應式 Web 元件的抽象層。這意味着您可以使用幾種不同的底層響應式實作。預設為 Reactor,我們将使用它進行示範。

首先,我們将使用 Spring 指令行工具初始化一個新應用程式。有幾種方法可以安裝這個工具,但我喜歡使用 SDKMan。您需要安裝 Java 17+。您可以在此處找到針對您的作業系統安裝 SDKMan 的說明。安裝後,您可以使用以下指令添加 Spring CLI $ sdk i springboot:現在該指令$ spring --version應該可以工作了。

要啟動新應用程式,請鍵入:

$ spring init --dependencies=webflux --build=maven --language=java spring-reactive           

接下來,cd進入spring-reactive目錄。Spring 為我們建立了一個簡單的布局,包括src/main/java/com/example/springreactive2/DemoApplication.java.

讓我們修改此類以添加反應式端點處理程式,如清單 1 所示。

清單 1. 添加 RESTful 端點

package com.example.springreactive;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
  @RestController
  public class EchoController {
    @GetMapping("/hello")
    public Mono<String> hello() {
      return Mono.just("Hello, InfoWorld!");
    }
    @GetMapping("echo/{str}")
    public Mono<String> echo(@PathVariable String str) {
      return Mono.just("Echo: " + str);
    }
    @GetMapping("echoquery")
    public Mono<String> echoQuery(@RequestParam("name") String name) {
      return Mono.just("Hello, " + name);
    }
  }
}           

注意:在 Reactor 庫中,Mono是指“一進制值”的類型。

他們@SpringBootApplication為我們處理了大部配置設定置。我們使用一個EchoController通過@RestController注釋調用的内部類,讓 Spring 知道我們正在使用哪些端點。

清單 1 中有三個示例,每個示例都映射到一個帶有@GetMapping. 第一個,/hello簡單地在響應中寫入問候語。第二個/echo/{str}示範如何擷取 URL 參數(路徑變量)并在響應中使用它。第三個,/echoquery展示了如何擷取請求參數(URL 中問号後面的值)并使用它。

在每種情況下,我們都依靠Mono.just()方法來描述響應。這是在 Reactor 架構中建立事件生成器的簡單方法。它說:使用參數中找到的單個事件建立一個事件生成器,并将其交給所有訂閱者。在這種情況下,訂閱者由 Spring WebFlux 架構和托管它的非阻塞伺服器處理。簡而言之,我們可以通路完全非阻塞的響應管道。

入站請求處理也完全基于非阻塞 IO。這使得擴充伺服器可能非常高效,因為沒有阻塞線程來限制并發性。特别是在實時系統中,非阻塞 IO 對于整體吞吐量非常重要。

Spring WebFlux預設使用Netty 伺服器。如果您願意,可以使用其他伺服器(例如 Undertow)或 Servlet 3.1 容器(例如 Tomcat)。有關伺服器選項的更多資訊,請參閱 WebFlux 文檔。

反應式程式設計示例

響應式程式設計需要一整套思維方式和一組概念,我們在這裡不會對其進行探讨。相反,我們将通過一些示例來揭示這種程式設計風格的關鍵方面。

首先,讓我們建立一個接受檔案上傳文章并将内容寫入磁盤的端點。您可以在清單 2 中看到該方法及其導入。代碼的其餘部分保持不變。

清單 2. 接受并寫入檔案

import org.springframework.http.MediaType; 
import org.springframework.http.codec.multipart.FilePart; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.RequestPart; 
import org.springframework.web.bind.annotation.RestController; 
import org.springframework.util.FileSystemUtils; 
import reactor.core.publisher.Flux; 
import reactor.core.publisher.Mono;

@PostMapping(value = "/writefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<String> writeFile(@RequestPart("file") Flux<FilePart> filePartFlux) {
        Path path = Path.of("/tmp/file.txt");

        // Delete the existing file if it already exists
        FileSystemUtils.deleteRecursively(path.toFile());

        // Save the file parts to the specified path
        return filePartFlux
                .flatMap(filePart -> filePart.transferTo(path))
                .then(Mono.just("File saved: " + path.toString()));
    }           

該writeFile()方法用 注釋@PostMapping,并且配置為接受分段表單上傳。到目前為止,這是一個正常的 Spring Web 配置。WebFlux 允許我們在方法參數中使用@RequestPart類型為 的注釋Flux<FilePart>。這讓我們可以使用 Flux 以反應式、非阻塞的方式接受多部分塊。

有了這個filePartFlux,我們就可以使用反應式flatMap方法将其寫入磁盤:filePartFlux.flatMap(filePart -> filePart.transferTo(path))。多部分檔案的每個“事件”都會傳遞給transferTo要添加到檔案中的函數。這是一個非常慣用的反應操作。

使用高階函數(例如flatMap轉換和處理事件流)是反應式程式設計的典型。它可以被視為具有事件生産者、訂閱者和轉換器,例如flatMap. 通過擷取一個或多個流并使用元函數鍊對其進行操作,您可以使用相對簡單的文法實作強大的效果。

要測試新端點,您可以使用 CURL 指令,如清單 3 所示。

清單 3. 使用 CURL 測試 writeFile 端點

$ echo "Facing it, always facing it, that’s the way to get through." >> testfile.txt
$ curl -X POST -F "file=@./testfile.txt" http://localhost:8080/writefile
File saved: /tmp/file.txtmatthewcarltyson@dev3:~/spring-reactive2

$ cat /tmp/file.txt           

在清單 3 中,我們建立一個testfile.txt包含一些内容的檔案(“面對它,始終面對它,這就是通過的方法”),然後将其發送到端點,接收響應,并驗證新檔案内容。

使用反應式 HTTP 用戶端

現在,讓我們建立一個接受 ID 參數的端點。我們将使用 Spring 反應式 HTTP 用戶端在SWAPI (星球大戰 API)上向該 ID 處的角色送出請求,然後将角色資料發送回使用者。您可以在清單 4 中看到這個新apiChain()方法及其導入。

清單 4. 使用響應式 Web 用戶端

import org.springframework.web.reactive.function.client.WebClient;

@GetMapping("character/{id}")
  public Mono<String> getCharacterData(@PathVariable String id) {
    WebClient client = WebClient.create("https://swapi.dev/api/people/");
    return client.get()
      .uri("/{id}", id)
      .retrieve()
      .bodyToMono(String.class)
      .map(response -> "Character data: " + response);
  }           

現在,如果您導航到localhost:8080/character/10,您将獲得歐比旺·克諾比的傳記資訊。

清單 4 的工作方式是接受 ID 路徑參數并使用它向類送出請求WebClient。在本例中,我們正在為請求建立一個執行個體,但您可以WebClient使用基本 URL 建立一個執行個體,然後在許多路徑中重複使用它。我們将 ID 放入路徑中,然後調用retrieve,bodyToMono()将響應轉換為 Mono。請記住,這一切仍然是非阻塞和異步的,是以等待 SWAPI 響應的代碼不會阻塞線程。最後,我們用來map()制定傳回給使用者的響應。

所有這些示例的總體效果是啟用從伺服器一直到代碼的高性能堆棧,而無需大驚小怪。

結論

響應式程式設計是一種與更熟悉的聲明式風格不同的思維方式,這可能會使推理簡單場景變得更加困難。找到了解反應式程式設計的程式員也更難。一般來說,良好的技術或業務需求應該決定您是使用反應式架構還是更标準的架構。

關注并回複1即可領取【Java學習資料大禮包】