【Web】Spring WebFlux

  SpringWebflux是SpringFramework5.0添加的新功能,WebFlux自己追隨當下最火的Reactive Programming而誕生的框架,那麼本篇就來簡述一下這個框架究竟是作什麼的java

 

1、關於WebFlux

  咱們知道傳統的Web框架,好比說:struts2,springmvc等都是基於Servlet API與Servlet容器基礎之上運行的,在Servlet3.1以後纔有了異步非阻塞的支持。而WebFlux是一個典型非阻塞異步的框架,它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來講,它能夠運行在諸如Netty,Undertow及支持Servlet3.1的容器上,所以它的運行環境的可選擇行要比傳統web框架多的多。react

  根據官方的說法,webflux主要在以下兩方面體現出獨有的優點:git

  1)非阻塞式github

    其實在servlet3.1提供了非阻塞的API,WebFlux提供了一種比其更完美的解決方案。使用非阻塞的方式能夠利用較小的線程或硬件資源來處理併發進而提升其可伸縮性web

  2) 函數式編程端點spring

    老生常談的編程方式了,Spring5必須讓你使用java8,那麼函數式編程就是java8重要的特色之一,而WebFlux支持函數式編程來定義路由端點處理請求。編程

 

2、SpringMVC與SpringWebFlux

咱們先來看官網的一張圖:tomcat

  它們均可以用註解式編程模型,均可以運行在tomcat,jetty,undertow等servlet容器當中。可是SpringMVC採用命令式編程方式,代碼一句一句的執行,這樣更有利於理解與調試,而WebFlux則是基於異步響應式編程,對於初次接觸的碼農們來講會不習慣。對於這兩種框架官方給出的建議是:架構

  1)若是原先使用用SpringMVC好好的話,則不必遷移。由於命令式編程是編寫、理解和調試代碼的最簡單方法。由於老項目的類庫與代碼都是基於阻塞式的。

  2)若是你的團隊打算使用非阻塞式web框架,WebFlux確實是一個可考慮的技術路線,並且它支持相似於SpringMvc的Annotation的方式實現編程模式,也能夠在微服務架構中讓WebMvc與WebFlux共用Controller,切換使用的成本至關小

  3)在SpringMVC項目裏若是須要調用遠程服務的話,你不妨考慮一下使用WebClient,並且方法的返回值能夠考慮使用Reactive Type類型的,當每一個調用的延遲時間越長,或者調用之間的相互依賴程度越高,其好處就越大

  我我的意見是:官網明確指出,SpringWebFlux並非讓你的程序運行的更快(相對於SpringMVC來講),而是在有限的資源下提升系統的伸縮性,所以當你對響應式編程很是熟練的狀況下並將其應用於新的系統中,仍是值得考慮的,不然仍是老老實實的使用WebMVC吧

 

3、Reactive Spring Web

  在這裏定義了最基本的服務端接口:HttpHandler和WebHandler

  HttpHandler

  HttpHandler定義了最基本的處理Http請求行爲,這個接口主要做用是處理Http請求並將結果作出響應,下面這個表格是說明了Server API的使用方式及何種方式進行響應式流支持的:

Server name Server API used Reactive Streams support

Netty

Netty API

Reactor Netty

Undertow

Undertow API

spring-web: Undertow to Reactive Streams bridge

Tomcat

Servlet 3.1 non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[]

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

Jetty

Servlet 3.1 non-blocking I/O; Jetty API to write ByteBuffers vs byte[]

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

Servlet 3.1 container

Servlet 3.1 non-blocking I/O

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

 

  WebHandler

  WebHandler定義了Web請求必要一些處理行爲,你們不妨想一想看:WebFlux已經脫離了Servlet API,那麼使用WebFlux時遇到會話機制怎麼辦,想要對請求過濾處理怎麼辦或者想要處理靜態資源怎麼辦等等,那麼WebHandler就是作這個事情的。其實在HttpHandler的基本實現類經過適配器模式及裝飾模式也間接的實現了WebHandler接口:

   WebHandler常見的實現類,我在這裏列舉一下:

   WebHandlerDecorator:WebHandler的裝飾器,利用裝飾模式實現相關功能的擴展

   HttpWebHandlerAdapter: 進行Http請求處理,同時也是HttpHandler的實現類

   FilteringWebHandler:經過WebFilter進行過濾處理的類,相似於Servlet中的Filter

   ExceptionHandlingWebHandler: 針對於異常的處理類

   ResourceWebHandler:用於靜態資源請求的處理類

   DispatcherHandler:請求的總控制器,相似於WebMVC中的DispatcherServlet

  

  

 

 4、實現WebFlux示例

創建SpringBoot項目,注意SpringBoot版本必須爲2.0.0+,在build.gradle配置文件裏寫:

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

基於Annotated Controller方式實現

WebFluxConfig配置:

複製代碼
package com.hzgj.framework.study.springwebflux.web.reactive;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;

@Configuration
@ComponentScan
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {

    @Bean
    public WebHandler webHandler(ApplicationContext applicationContext) {
        DispatcherHandler dispatcherHandler = new DispatcherHandler(applicationContext);
        return dispatcherHandler;
    }


}
複製代碼

在這裏咱們建立一個WebHandler並使用@EnableWebFlux打開相關功能。咱們能夠發現與SpringMVC的WebConfig配置真的好像

 

Controller:

複製代碼
package com.hzgj.framework.study.springwebflux.web.reactive.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    @GetMapping("/index")
    public String index() {
        return "index";
    }
}
複製代碼

  在這裏與SpringMVC定義的Controller無異

Main方法:

複製代碼
package com.hzgj.framework.study.springwebflux.web.reactive;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.ipc.netty.http.server.HttpServer;

import java.io.IOException;

/**
 * 基於Reactor Netty實現WebFlux服務
* @author chen.nie
* @date 2018/7/13
**/
public class SpringWebfluxApplication {

    public static void main(String[] args) throws IOException {

        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(WebFluxConfig.class);
        //經過ApplicationContext建立HttpHandler
        HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
        ReactorHttpHandlerAdapter httpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer.create("localhost",8080).newHandler(httpHandlerAdapter).block();
        System.in.read();
    }
}
複製代碼

  程序啓動成功後便可經過http://localhost:8080/index拿到對應結果

函數式編程方式

  使用這種方式請先了解Java8提供的函數式編程特性。那麼咱們先編寫一個簡單的Handler

複製代碼
package com.hzgj.framework.study.springwebflux.web.reactive.handler;

import org.springframework.beans.BeanUtils;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import static org.springframework.http.MediaType.*;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

/**
 * 相似於Controller,處理用戶請求的真實邏輯
 */
public class StudentHandler {


    public static Mono<ServerResponse> selectStudent(ServerRequest request) {
        Student studentBody = new Student();
        request.bodyToMono(Student.class).subscribe(student -> BeanUtils.copyProperties(student, studentBody));
        return ok().contentType(APPLICATION_JSON_UTF8).body(fromObject(studentBody));
    }

    public static Mono<ServerResponse> insertStudent(ServerRequest request){
        return ok().contentType(TEXT_PLAIN).body(fromObject("success"));

    }
    private static class Student {
        private Integer id;
        private String name;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
複製代碼

  這個Handler相似於Controller的做用,在這裏的返回值均爲Mono類型,其中ServerRequest和ServerResponse,你們能夠先理解爲WebFlux替代ServletRequest與ServletResponse對象的,並且這些類可以支持異步。

  Main方法裏咱們建立的HttpHandler的方式須要進行改變一下,一樣用函數式方式進行編寫:

複製代碼
package com.hzgj.framework.study.springwebflux.web.reactive;

import com.hzgj.framework.study.springwebflux.web.reactive.handler.StudentHandler;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.ipc.netty.http.server.HttpServer;

import java.io.IOException;

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;


public class FunctionRouteApplication {

    public static void main(String[] args) throws IOException {

        HttpHandler httpHandler = toHttpHandler(
                route(POST("/selectStudent").and(accept(MediaType.APPLICATION_JSON_UTF8)), StudentHandler::selectStudent).
                        and(route(GET("/saveStudent"), StudentHandler::insertStudent)));
        ReactorHttpHandlerAdapter httpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer.create("localhost", 8080).newHandler(httpHandlerAdapter).block();
        System.in.read();
    }
}
複製代碼

啓動成功後,咱們用postman測試一下

此時咱們能夠看到使用函數式編程建立路由端點,也能夠實現一樣的功能。

相關文章
相關標籤/搜索