WebFlux使用函數語言程式設計

語言: CN / TW / HK

本篇主要內容:

  • HandlerFunction的使用

概述

Spring WebFlux包括WebFlux.Fn是一種輕量級函數語言程式設計模型,其中函式用於路由和處理請求,契約設計為不可變。它是基於註釋的程式設計模型的另一種選擇,但在其他方面執行在相同的Reactive Core基礎上。

在WebFlux.Fn,HTTP請求由HandlerFunction處理:該函式接受ServerRequest並返回延遲的ServerResponse(即Mono<ServerResponce>)。請求和響應物件都有不可變的契約,提供對HTTP請求和響應的JDK 8友好訪問。HandlerFunction相當於基於註釋的程式設計模型中@RequestMapping方法的主體。

傳入的請求被路由到一個帶有RouterFunction的處理函式:一個接受ServerRequest並返回延遲HandlerFunction(即Mono<HandlerFunction>)的函式。當路由器函式匹配時,返回處理函式;否則為空Mono。RouterFunction相當於@RequestMapping註釋,但主要區別在於router函式不僅提供資料,還提供行為。

RouterFunctions.route()提供了一個路由器生成器,可以方便建立路由器,如下例所示:

示例:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
  .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
  .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
  .POST("/person", handler::createPerson)
  .build();
public class PersonHandler {
  public Mono<ServerResponse> listPeople(ServerRequest request) {
    // todo 業務處理邏輯
  }
  public Mono<ServerResponse> createPerson(ServerRequest request) {
    // todo 業務處理邏輯
  }
  public Mono<ServerResponse> getPerson(ServerRequest request) {
    // todo 業務處理邏輯
  }
}

執行RouterFunction的一種方法是將其轉換為HttpHandler,並通過內建伺服器介面卡之一進行安裝:

RouterFunctions.toHttpHandler(RouterFunction)。

RouterFunctions.toHttpHandler(RouterFunction,HandlerStrategies)。

大多數應用程式都可以通過WebFlux Java配置執行。

HandlerFunction

​ServerRequest和ServerResponse是不可變的介面,提供對HTTP請求和響應的JDK 8友好訪問。請求和響應都提供了針對體流的反應流背壓。請求主體用反應器Flux或Mono表示。響應主體由任何反應流Publisher表示,包括Flux和Mono。

  • ServerRequest​

​ServerRequest提供對HTTP方法、URI、頭和查詢引數的訪問,而對正文的訪問是通過正文方法提供的。

下面的例子將請求體提取為Mono:

Mono<String> string = request.bodyToMono(String.class);

以下示例將正文提取為Flux(或Kotlin中的Flow),其中Person物件從某種序列化形式(如JSON或XML)解碼:

Flux<Person> people = request.bodyToFlux(Person.class);

前面的例子是使用更通用的ServerRequest.body(BodyExtractor)的快捷方式,它接受BodyExtractor函式策略介面。實用工具類BodyExtractors提供了對多個例項的訪問。例如,前面的例子也可以寫成這樣:

Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));

下面的例子展示瞭如何訪問表單資料:

Mono<MultiValueMap<String, String>> map = request.formData();

下面的例子展示瞭如何將多部分資料作為對映訪問:

Mono<MultiValueMap<String, Part>> map = request.multipartData();

下面的例子展示瞭如何以流方式一次訪問多個部分:

Flux<Part> parts = request.body(BodyExtractors.toParts());
  • ServerResponse

ServerResponse提供對HTTP響應的訪問,因為它是不可變的,所以你可以使用構建方法來建立它。你可以使用構造器來設定響應狀態、新增響應頭或提供響應正文。下面的例子建立了一個包含JSON內容的200 (OK)響應:

Mono<Person> person = Mono.just(new Person("張三", 12))
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);

下面的例子展示瞭如何構建一個201 (CREATED)響應,它有一個Location頭,沒有正文:

URI location = ...
ServerResponse.created(location).build();

根據所使用的編解碼器,可以通過傳遞提示引數來定製體的序列化或反序列化方式。例如,要指定一個Jackson JSON檢視:

ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
  • Handler Classes

我們可以將處理函式編寫為lambda,如下示例所示。

HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().bodyValue("Hello World");

這很方便,但在應用程式中,我們需要多個函式,而多個內聯lambda會變得很混亂。因此,將相關的處理程式函式組合到一個處理程式類中是很有用的,這個處理程式類在基於註釋的應用程式中具有類似於@Controller的角色。例如,下面的類公開了一個響應式Person儲存庫:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
    private final PersonRepository repository;
    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }
    public Mono<ServerResponse> listPeople(ServerRequest request) { 
        Flux<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }
    public Mono<ServerResponse> createPerson(ServerRequest request) { 
        Mono<Person> person = request.bodyToMono(Person.class);
        return ok().build(repository.savePerson(person));
    }
    public Mono<ServerResponse> getPerson(ServerRequest request) { 
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
  • Validation

一個 functional endpoint 可以使用Spring的驗證工具對請求體應用驗證。例如,給定一個Person的自定義Spring Validator實現:

public class PersonHandler {
    private final Validator validator = new PersonValidator(); 
    // ...

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); 
        return ok().build(repository.savePerson(person));
    }
    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString()); 
        }
    }
}

總結:

  1. 路由函式中 HandlerFunction 的使用。
  2. ServerRequest,ServerResponse使用示例。