1. 程式人生 > >Spring 5.0系列:初探響應式Web框架WebFlux

Spring 5.0系列:初探響應式Web框架WebFlux

1. WebFlux介紹

Spring WebFlux是SpringFramework5.0新增的新功能,WebFlux本身追隨Reactive Programming而誕生的框架。

傳統的Web框架,比如說:struts2,springmvc等都是基於Servlet API與Servlet容器基礎之上執行的,在Servlet3.1之後才有了非同步非阻塞的支援。而WebFlux是一個典型非阻塞非同步的框架,它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來說,它可以執行在諸如Netty,Undertow及支援Servlet3.1的容器上,因此它的執行環境的可選擇行要比傳統web框架多。
WebFlux


左側是傳統的基於Servlet的Spring Web MVC框架,右側是5.0版本新引入的基於Reactive Streams的Spring WebFlux框架,從上到下依次是Router Functions,WebFlux,Reactive Streams三個新元件。

  • Router Functions: 對標@Controller,@RequestMapping等標準的Spring MVC註解,提供一套函式式風格的API,用於建立Router,Handler和Filter。
  • WebFlux: 核心元件,協調上下游各個元件提供響應式程式設計支援。
  • Reactive Streams: 一種支援背壓(Backpressure)的非同步資料流處理標準,主流實現有RxJava和Reactor,Spring WebFlux預設整合的是Reactor。

在Web容器的選擇上,Spring WebFlux既支援像Tomcat,Jetty這樣的的傳統容器(前提是支援Servlet 3.1 Non-Blocking IO API),又支援像Netty,Undertow那樣的非同步容器。不管是何種容器,Spring WebFlux都會將其輸入輸出流適配成Flux格式,以便進行統一處理。

值得一提的是,除了新的Router Functions介面,Spring WebFlux同時支援使用老的Spring MVC註解宣告Reactive Controller。和傳統的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequest和ServerHttpResponse,而不再是Spring MVC裡的HttpServletRequest和HttpServletResponse。
非阻塞:

  • Servlet3.1才開始支援非同步處理,在此之前Servlet 執行緒需要一直阻塞,直到業務處理完畢再輸出響應,然後結束 Servlet執行緒。非同步處理的作用是在接收到請求之後,Servlet 執行緒可以將耗時的操作委派給另一個執行緒來完成,Servlet 執行緒繼續做其他的事情。主要應用場景是針對業務處理較耗時的情況,這樣就可以通過少量的執行緒處理更多的請求,實提現高吞吐量。

  • 當然WebFlux提供了一種比其更完美的解決方案。使用非阻塞的方式可以利用較小的執行緒或硬體資源來處理併發進而提高其可伸縮性

2. 兩種不同的程式設計模型

WebFlux服務端支援兩種不同的程式設計模型:

  • 第一種是 Spring MVC 中使用的基於 Java 註解的方式;
  • 第二種是基於 Java 8 的 lambda 表示式的函數語言程式設計模型。

這兩種程式設計模型只是在程式碼編寫方式上存在不同。它們執行在同樣的反應式底層架構之上,因此在執行時是相同的

2.1 註解模式

與Spring MVC中的註解幾乎一樣,下面程式碼中中的 FirstFluxController 是 REST API 的控制器,通過@RestController 註解來宣告。在 FirstFluxController 中聲明瞭一個 URI 為/index 的對映。其對應的方法 index()的返回值是 Mono型別,其中包含的字串”hello web-flux”會作為 HTTP 的響應內容。

WebFlux也支援相同的@Controller程式設計模型和Spring MVC中使用的相同註釋。 主要區別在於底層核心,即HandlerMapping,HandlerAdapter,是非阻塞的,並且在響應的ServerHttpRequest和ServerHttpResponse上操作,而不是在HttpServletRequest和HttpServletResponse上操作。 以下是示例:

@RestController
public class FirstFluxController {
    @GetMapping("/index")
    public Mono<String> index() {
        return Mono.just("hello web-flux!");
    }
}
2.2 函式式路由模式

在函數語言程式設計模型中,每個請求是由一個函式來處理的, 通過介面 org.springframework.web.reactive.function.server.HandlerFunction 來表示。HandlerFunction 是一個函式式介面,其中只有一個方法 Mono handle(ServerRequest request),因此可以用 labmda 表示式來實現該介面。IndexHandller中的index方法就可以通過lamda表示式包裝為HandlerFunction的實現。

幾個重要的概念:

  • 介面 ServerRequest 表示的是一個 HTTP 請求。通過該介面可以獲取到請求的相關資訊,如請求路徑、HTTP 頭、查詢引數和請求內容等。
  • 方法 handle 的返回值是一個 Mono物件。
  • 介面 ServerResponse 用來表示 HTTP 響應。ServerResponse 中包含了很多靜態方法來建立不同 HTTP 狀態碼的響應物件。
@Component
public class IndexHandller {
        public Mono<ServerResponse> index(ServerRequest req) {
            return ok().body(Flux.just("functional rounter,base on netty"),String.class);
        }
}

IndexHandller可以理解為webMVC中的Service類,它是業務邏輯的實現。在後面的路由方法中會呼叫IndexHandller中的方法。有了Handller之後,需要為Handller中的方法進行路由,也就是呼叫這些方法的條件。如/index /usercent之類的。

路由是通過函式式介面 org.springframework.web.reactive.function.server.RouterFunction來完成的。介面RouterFunction的方法Mono<HandlerFunction<T extends ServerResponse>> route(ServerRequest request)對每個 ServerRequest,都返回對應的 0 個或 1 個 HandlerFunction物件,以 Mono<HandlerFunction>來表示。當找到對應的 HandlerFunction時,該HandlerFunction被呼叫來處理該ServerRequest,並把得到的ServerResponse返回。在使用 WebFlux 的 Spring Boot 應用中,只需要建立 RouterFunction 型別的 bean,就會被自動註冊來處理請求並呼叫相應的 HandlerFunction。

@Configuration
public class webfluxconfig  {

    @Bean
    @Autowired
    public RouterFunction<ServerResponse> routes( IndexHandller indexHandller) {
            return route(
                    GET("/homepage").and(accept(MediaType.APPLICATION_JSON)), indexHandller::index
                    );

    }

}

通過SpringBoot啟動,看起來我們沒做什麼事情,似乎和SpringMVC沒啥區別啊!就是SpringMVC+Reactive Stream?其實不是的,只是SpringBoot的自動配置太強大了,下面就不使用Spring boot,手動的編寫Server、Handler和Router。

3. without Spring Boot

Server

Spring WebFlux在Tomcat,Jetty,Servlet 3.1+容器以及非Servlet執行時(如Netty和Undertow)上受支援。 所有伺服器都適用於低階通用API,因此可以跨伺服器支援更高級別的程式設計模型。
Spring Boot有一個WebFlux啟動器,可以自動執行這些步驟。預設情況下使用Netty作為伺服器。
下面對兩種Server的例子:

/**
 * @author TheLudlows
 * 
 */
public class Server {

    public static final String HOST = "localhost";

    public static final int PORT = 8080;

    private Routes routes = new Routes();

    public static void main(String[] args) throws Exception {
        Server server = new Server();
     //   server.startReactorServer();
        server.startTomcatServer();

        System.out.println("Press ENTER to exit.");
        System.in.read();
    }


    public void startReactorServer() throws InterruptedException {
        RouterFunction<ServerResponse> route = routes.rout();
        HttpHandler httpHandler = toHttpHandler(route);

        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer server = HttpServer.create(HOST, PORT);
        server.newHandler(adapter).block();
    }

    public void startTomcatServer() throws LifecycleException {
        RouterFunction<?> route = routes.rout();
        HttpHandler httpHandler = toHttpHandler(route);

        Tomcat tomcatServer = new Tomcat();
        tomcatServer.setHostname(HOST);
        tomcatServer.setPort(PORT);
        Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
        ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
        Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
        rootContext.addServletMapping("/", "httpHandlerServlet");
        tomcatServer.start();
    }

}

重要的解釋:

  • HttpHandler - 用於HTTP請求處理的基本通用API,具有非阻塞I / O和(反應流)背壓,以及每個支援的伺服器的介面卡。
  • WebHandler API - 稍高級別,但仍然是伺服器請求處理的通用API,它是更高級別的程式設計模型(如帶註釋的控制器和功能端點)的基礎。
  • ReactorHttpHandlerAdapter:使HttpHandler支援Reactor Netty通道處理功能。就是一個介面卡
    • ServletHttpHandlerAdapter:使HttpHandler支援Servlet 3.1非同步非阻塞I / O。同上。
Router
/**
 * @author TheLudlows
 *
 */
public class Routes {

    public RouterFunction<ServerResponse> rout() {
        CustomerRepository repository = new CustomerRepositoryImpl();
        CustomerHandler handler = new CustomerHandler(repository);

        return nest(
                path("/customer"),
                //靜態方法accept用來設定接收或者返回的請求型別 
                nest(accept(APPLICATION_JSON),
                        route(GET("/{id}"), handler::getCustomer)
                        //連線方法:and(RouterFunction),andOther(RouterFunction),nest(),andNest(),andRoute()等,可以進行組合
                        .andRoute(method(HttpMethod.GET), handler::listCustomer))
                        .andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::saveCustomer)
        ).andNest(
                path("/product"),
                route(path("/"), serverRequest ->
                        ServerResponse.ok().contentType(APPLICATION_JSON)
                                .body(fromPublisher(Flux.just(new Product(1, "PC", 1000.00)), Product.class))
                )
        );
    }
}

RouterFunction<ServerResponse>建立是通過RouterFunctions的靜態方法route和nest兩個方法建立的。

  1. RouterFunctions.route(RequestPredicate predicate, HandlerFunction<T> handlerFunction)
    其中 RequestPredicate 是一個函式式介面,接受一個’T’型別返回一個布林型別。 大多數情況,我們都可以通過RequestPredicates類的靜態方法來建立這個物件,比如:

    public static RequestPredicate GET(String pattern) {
            return method(HttpMethod.GET).and(path(pattern));
        }

    HandlerFunction<T>也是一個函式式介面,前面函式路由模組也詳細說到過這個介面。它接受一個ServerResponse的子類返回Mono,可以把這個物件當作實際處理邏輯的部分。

  2. public static <T extends ServerResponse> RouterFunction<T> nest(RequestPredicate predicate, RouterFunction<T> routerFunction)nest 方法同樣有一個 RequestPredicate 但是第二個引數變成了 RouterFunction routerFunction,這個方法的作用是為了分支uri。在RESTFUL風格的介面中,會有目錄層級的概念,這裡的nest方法就可以理解為層級的關係,比如上述的”/person”後面的”/{id}” 就是相當於 “/person/{id}”

關於Handller和Dao在此就不做介紹了,詳細見程式碼:git地址,Handller的基本使用在前文已經介紹過了,至此WebFlux的兩種程式設計風格以及手動建立WebFlexServer和Rounter說完了,但這僅僅算是webFlux的入門。後面會分享一些webFlux的原理和實踐相關的文章^_^