Spring WebFlux 的設計及工作原理剖析

klblog發表於2019-06-24

Spring 5釋出有兩年了,隨Spring 5一起釋出了一個和Spring WebMvc同級的Spring WebFlux。這是一個支援反應式程式設計模型的新框架體系。反應式模型區別於傳統的MVC最大的不同是非同步的、事件驅動的、非阻塞的,這使得應用程式的併發效能會大大提高,單位時間能夠處理更多的請求。這裡不講WebFlux是怎麼用的,有什麼用,這類文章網上有太多了,而且都寫的非常不錯。下面主要看下WebFlux是怎麼從無到有,框架怎麼設計的,已期能夠更靈活的使用WebFlux。

Spring最牛逼的地方就是,無論啥東西,都可以無縫的整合到Spring。這得益於Spring體系優良的抽象封裝能力。WebFlux框架也一樣,底層實現其實不是Spring的,它依賴reactor和netty等。Spring做的就是通過抽象和封裝,把reactor的能力通過你最熟悉不過的Controller來使用。而且不侷限於此,除了支援和Spring Mvc一樣的控制器編碼模式,還支援路由器模式(RouterFunctions),還支援端點模式(EndPoint)等。WebFlux所有功能其實內部只由幾個抽象類構建而成:

  • org.springframework.boot.web.reactive.server.ReactiveWebServerFactory
  • org.springframework.boot.web.server.WebServer
  • org.springframework.http.server.reactive.HttpHandler
  • org.springframework.web.reactive.HandlerMapping
  • org.springframework.web.server.WebHandler

WebServer

我們從最底層往上層剖析,WebServer見名之意,就是Reacive伺服器的抽象類,它定義了服務的基本方法行為,包含啟動,停止等介面。結構如下:

public interface WebServer {
    void start() throws WebServerException;
    void stop() throws WebServerException;
    int getPort();
}

Spring預設有五個WebServer的實現,預設的不特別指定情況下,在spring-boot-starter-webflux自帶的是Netty的實現,其實現類如下:

ReactiveWebServerFactory

對應WebServer,每個實現都會有一個工廠類對應,主要準備建立WebServer例項的資源,如NettyReactiveWebServerFactory生產WebServer方法:

    public WebServer getWebServer(HttpHandler httpHandler) {
        HttpServer httpServer = createHttpServer();
        ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(
                httpHandler);
        return new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout);
    }

可以看到,在建立WebServer例項時,傳入了一個入參,HttpHandler。而且進而傳入了一個HttpHandlerAdapter例項裡,這是因為每個WebServer的接收處理介面的介面卡是不一樣的,在每個不同的WebServer工廠裡通過不過的介面卡去適配不同的實現。最後轉化成統一設計的HttpHandler裡,見下面。

HttpHandler

接下來看下HttpHandler,上面在建立WebServer的時候,傳了一個入參,型別就是Httphandler。為了適配不同的WebServer請求響應體,Spring設計了HttpHandler用來轉化底層的Http請求響應語義,用來接收處理底層容器的Http請求。其結構如下:

public interface HttpHandler {
    Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);
}

如在Netty的實現中,Netty接收請求處理的介面卡ReactorHttpHandlerAdapter的apply中轉化的虛擬碼如下:

public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) {
        NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc());
        try {
            ReactorServerHttpRequest request = new ReactorServerHttpRequest(reactorRequest, bufferFactory);
            ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory);

            if (request.getMethod() == HttpMethod.HEAD) {
                response = new HttpHeadResponseDecorator(response);
            }

            return this.httpHandler.handle(request, response)
                    .doOnError(ex -> logger.trace(request.getLogPrefix() + "Failed to complete: " + ex.getMessage()))
                    .doOnSuccess(aVoid -> logger.trace(request.getLogPrefix() + "Handling completed"));
        }
}

WebHandler

其實一般來講設計到HttpHandler這一層級基本就差不多了,有一致的請求體和響應體了。但是Spring說還不夠,對Web開發來講不夠簡潔,就又造了一個WebHandler,WebHandler架構更簡單,如下:

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

這回夠簡潔了,只有一個入參,那請求提和響應體去哪裡了呢?被包裝到ServerWebExchange中了。我麼看下當HttpHandler接收到請求後,是怎麼處理然後在呼叫WebHandler的,最終處理HttpHandler實現是HttpWebHandlerAdapter.java,通過其內部的createExchange方法將請求和響應體封裝在ServerWebExchange中了。其handle程式碼如下:

    public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
        if (this.forwardedHeaderTransformer != null) {
            request = this.forwardedHeaderTransformer.apply(request);
        }

        ServerWebExchange exchange = createExchange(request, response);

        LogFormatUtils.traceDebug(logger, traceOn ->
                exchange.getLogPrefix() + formatRequest(exchange.getRequest()) +
                        (traceOn ? ", headers=" + formatHeaders(exchange.getRequest().getHeaders()) : ""));

        return getDelegate().handle(exchange)
                .doOnSuccess(aVoid -> logResponse(exchange))
                .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
                .then(Mono.defer(response::setComplete));
    }

HandlerMapping

首先看下HandlerMapping的構造,可以看到就是根據web交換器返回了一個Handler物件

public interface HandlerMapping {
    String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
    Mono<Object> getHandler(ServerWebExchange exchange);
}

上面的“請求“已經到WebHandler了,那麼最終是怎麼到我們定義的控制器介面的呢?其實,沒有HandlerMapping,Spring WebFlux的功能也是完整的,也是可程式設計的,因為可以基於WebHandler直接編碼。我們最弄的一個閘道器最後就是直接走自定義的WebHandler,根本沒有HandlerMapping的什麼事情,但是你這麼做的話就失去了Spring編碼的友好性了。WebFlux的初始化過程中,會去Spring上下文中找name是“webHandler”的的WebHandler實現。預設情況下,Spring會在上下文中初始化一個DispatcherHandler.java的實現,Bean的name就是“webHandler”。這個裡面維護了一個HandlerMapping列表,當請求過來時會迭代HandlerMapping列表,返回一個WebHandler處理,程式碼如下:

    public Mono<Void> handle(ServerWebExchange exchange) {
        if (this.handlerMappings == null) {
            return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
        }
        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));
    }

上面mapping的內部結構如下:

上面箭頭指向的地方說明了為什麼WebFlux支援控制器和路由器模式模式的編碼,因為他們分別有實現的HandlerMapping,能夠在WebHandler的handler里路由到具體的業務方法裡。紅框中正是通過@Controller和@ResultMaping定義的介面資訊。

上面介紹了五個主要的抽象介面定義,以及功能。這五個介面在Spring WebFlux裡是靈魂一樣的存在。不過想要徹底的搞懂Web Flux的設計以及實現原理,僅僅瞭解上面這些介面定義是遠遠不夠的,看完上面介面的分析肯定有中模糊的似懂非懂的感覺,不著急,接下來分析下,在Spring Boot環境中,Spring WebFlux的啟動流程。

ReactiveWebServerApplicationContext

WebFlux的啟動都在Reactive的上下文中完成,和WebMvc類似,Mvc也有一個ServletWebServerApplicationContext,他們是同宗同脈的。ReactiveWebServerApplicationContext還有一個父類AnnotationConfigReactiveWebServerApplicationContext,在Spring boot啟動中,建立的就是這個父類的例項。在Spring boot的run()方法中建立上下文時有如下程式碼:

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

可以看到,當webApplicationType是REACTIVE時,載入的就是DEFAULT_REACTIVE_WEB_CONTEXT_CLASS。webApplicationType型別是通過識別你載入了哪個依賴來做的。熟悉Spring啟動流程的同學都知道,基礎 的Spring上下文是在AbstractApplicationContext的refresh()方法內完成的,針對不同的上下文文實現例項還會有一個onRefresh()方法,完成一些特定的Bean的例項化,如WebFlux的上下文例項就在onRefresh()中完成了WebServer的建立:

    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start reactive web server",
                    ex);
        }
    }
    private void createWebServer() {
        ServerManager serverManager = this.serverManager;
        if (serverManager == null) {
            this.serverManager = ServerManager.get(getWebServerFactory());
        }
        initPropertySources();
    }

WebFlux裡面啟動流程太複雜,全盤脫出寫的太長嚴重影響閱讀體驗。所以上面權當拋磚引玉,開一個好頭。不過,WebFlux的啟動流程節點博主都已分析並整理成流程圖了,結合上面的介面設計分析,搞懂WebFlux的設計及工作原理應該冒點問題。

高清大圖連結:https://www.processon.com/view/link/5d0763...

本作品採用《CC 協議》,轉載必須註明作者和本文連結

kl

相關文章