簡介 WebFlux


如果你想以 Reactive 風格來實作 Web 應用程式,從 Spring 5 開始,可以考慮使用 WebFlux,就撰寫本文的這個時間點,在 Spring 官方網站首頁中,有個技術堆疊示意圖,用來解釋 WebFlux 再好也不過!

簡介 WebFlux

有些人會以為,Spring WebFlux 是 Spring MVC 的替代方案,實際上並不是這麼一回事,雖然 WebFlux 也可以執行在 Servlet 容器上(因為必須得有容器層面的非同步支援,因此得是在 Servlet 3.1+ 以上的容器實作),然而,Spring WebFlux 最主要是針對非阻斷的設計模型,而 Spring MVC 是針對同步的設計模型,如果你曾經因為得在同步的 Web 容器之中,全面或大量地設計非同步方案而感到諸多不便,那 WebFlux 才會是你想要的,否則的話,你應該使用 Spring MVC 。

因此,採用 WebFlux,難處並不在於 API,實際上,你已經學會一半的 WebFlux 了,因為 WebFlux 可以採用與 Spring MVC 相同的註解模式來實作(另一個模式是函數式風格),也就是說,大部份在 Spring MVC 中使用的註解,都可以在 Web Flux 中使用,另一方面,WebFlux 基於 Spring 自家的 Reactive 實作,也就是 Reactor 專案,之前已經討論過一系列的 Reactor 專案,因此接觸 WebFlux 時,在 API 層面是很快就能上手了。

然而,真正難的地方在於,如何全面地以 Reactive 的思維來設計與撰寫程式,雖然說真要在 WebFlux 中,以同步的方式來實作,也是有方案可循,不過這並不能發揮 WebFlux 真正的優勢,也就是非同步處理時的效能表現,如果你一直以同步的思維來使用 WebFlux,那只會感到諸多違和、處處 workaround 的做法。

別為了 WebFlux 而 WebFlux,如果你的心智模型一直都掛在同步思維、Servlet 容器技術堆疊之上,乖乖使用 Spring MVC 吧!

正如上圖所示的,除了 WebFlux 之外,要能發揮 WebFlux 優勢,其他技術堆疊也得支援 Reactive 風格,例如 Spring 提供了 Spring Security Reactive,在資料庫層面,也有 ReactiveMongoRepository 之類的支援,不過還不全面,例如 JDBC 方面的支援,最好的情況下應該由資料庫驅動程式直接支援,然而,Java 標準本身尚未有正式的方案,之後文件還會談到這部份。

無論如何,先來看個 WebFlux 的基本示範吧!使用 Spring Boot 的話,只要選擇 Reactive Web 的 Starter 就可以了,build.gradle 裏主要相依的是:

implementation('org.springframework.boot:spring-boot-starter-webflux')

預設的啟動程式看來也沒什麼不同:

package cc.openhome;

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

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

那麼,來寫個 Hello 吧!

package cc.openhome;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

@SpringBootApplication
@RestController
public class HelloFluxApplication {

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

    @GetMapping("/hello/{name}") 
    public Mono<User> hello(@PathVariable("name") String name) {
        return Mono.just(new User(name));
    }
}

看起來跟 Spring MVC 沒什麼兩樣,User 也只是個自定義的簡單 POJO 而已,如前所述。

這是因為 WebFlux 可以採用 Spring MVC 註解模型,在官方的〈Web on Reactive Stack〉中有張圖,示意了兩者重疊之處:

簡介 WebFlux

當然,還是有不同的地方,也就是 hello 方法的傳回值是 Reactor 的 Mono 型態,WebFlux 本身會訂閱 FluxMono,在有資料的時候,對客戶端進行回應,在資料流結束後,關閉客戶端的連線,Spring Boot 預設會使用 Netty,因為這邊標示為 @RestController,回應會是 JSON 格式,例如,若請求 http://localhost:8080/hello/caterpillar,回應會是 {"name":"caterpillar"}

Spring MVC 的前端控制器是 DispatcherServlet,而 WebFlux 是 DispatcherHandler,實作了 WebHandler 介面:

package org.springframework.web.server;

import reactor.core.publisher.Mono;

import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange exchange);
}

ServerWebExchange 代表著一次 HTTP 請求回應,可以透過它來取得、設定相關請求回應訊息,之後會有機會直接使用它,DispatcherHandler 實作上,就只是取得 mapping、交鉿處理器與並處理結果:

...略
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    ...略

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (logger.isDebugEnabled()) {
            ServerHttpRequest request = exchange.getRequest();
            logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
        }
        return Flux.fromIterable(this.handlerMappings)
                .concatMap(mapping -> mapping.getHandler(exchange))
                .next()
                .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
                .flatMap(handler -> invokeHandler(exchange, handler))
                .flatMap(result -> handleResult(exchange, result));
    }
    ...略
}

儘早讓處理器方法與結果處理執行完畢,從 handler 方法返回,是有效運用 WebFlux 的 Reactive、非同步風格的基本原則。

你可以在 HelloFlux 找到以上的範例專案。