快速上手Spring Flux框架

一、前言

本文主要介紹基於SpringBoot如何快速上手使用SpringFlux框架開發WEB網站。

Spring 5.0在原有的Spring MVC Stack(又稱Servlet Stack)以外,又引入了新的WEB開發技術棧——Spring Flux Stack(又稱Reactive Stack),以滿足不同的應用程序及開發團隊的需求。

開發者一直在尋找最適合他們的應用程序的運行時、編程框架及架構。比如,有些用例最適合採用基於同步阻塞IO架構的技術棧,而另一些用例可能更適合於基於Reactive Streams響應式編程原則構建的異步的、非阻塞的技術棧。

後續將有系列文章深入介紹SpringFlux所採用的響應式編程原則及其代表實現ProjectReactor,希望通過系列文章的介紹,讓廣大讀者能夠在逐步使用SpringFlux的過程中,理解響應式編程原理及實現,進而能夠對項目應該選擇SpringMVC還是SpringWebFlux形成自己的判斷標準。

文章系列

<!-- more -->

二、快速上手

1、創建項目

打開 http://start.spring.io,來初始化一個Spring WebFlux項目吧。左側一列是Project Metadata,填上你的group名(我們使用net.yesdata吧),還有Artifact名(默認是demo);然後右側一列是Dependencies(依賴),我們輸入"reactive web",在得到的下拉框中選擇"Reacive Web",然後下方"Selected Dependencies"處會顯示我們選中的"Reactive Web"。然後點擊"Generate Project",瀏覽器會下載創建好的Spring Boot項目。加壓後用你喜歡的IDE(Eclipse或者IntelliJ IDEA等)。

通過start.spring.io創建reactive棧的項目

2、增加一個Controller

如果你熟悉Spring MVC,你一定對@Controller註解不陌生。即使不熟悉也沒關係,我會簡單介紹一下。
Spring WebFlux帶有兩種特徵,一種是函數式的(Functional),另一種是基於註解的(annotation-based)。函數式編程不太適合快速上手,我們先選擇基於註解。類似於Spring MVC模型,Spring WebFlux模型也使用@Controller註解,以及@RestController註解。

用IDE打開項目後,追加一個類:SampleController
然後增加如下代碼:

@RestController
@RequestMapping("/")
public class SampleController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

運行後,用瀏覽器訪問:http://localhost:8080/hello,將會在瀏覽器上看到"hello"字樣。

增加SampleController

SampleController執行結果

怎麼樣,是不是很簡單。

3、增加一個WebFilter

增加一個Filter試試看。在Spring WebFlux框架下,增加Filter是通過實現WebFilter接口實現的。

@Component
public class FirstWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        serverWebExchange.getAttributes().put("User", "jerry");
        return webFilterChain.filter(serverWebExchange);
    }
}

三、深入瞭解一下SpringBoot啓動Spring WebFlux的過程

1、準備工作

你需要從Gitubhttps://github.com/spring-projects/spring-framework下載Spring WebFlux的源碼,以便進行後續分析。

2、@EnableWebFlux

回顧一下通過http://start.spring.io創建項目中,DemoApplication啓動類的註解中,有一個註解是:@EnableWebFlux。SpringBoot就是通過這個註解,來啓動Spring WebFlux的。

@EnableWebFlux
@SpringBootApplication
public class DemoApplication {

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

再看一下EnableWebFlux的定義(可以到SpringWebFlux源碼中找到),如下,我們注意到它引入了DelegatingWebFluxConfiguration類。看來祕密會藏在DelegatingWebFluxConfiguration類裏呢。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebFluxConfiguration.class)
public @interface EnableWebFlux {
}

打開DelegatingWebFluxConfiguration.java文件(可以到SpringWebFlux源碼中找到),然後你我們發現它的父類是WebFluxConfigurationSupport類。我們先來看看這個父類。

@Configuration
public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport {
......
}

下面是父類WebFluxConfigurationSupport實現的代碼片段。我們注意到,它向Spring Bean容器中,注入了兩個Bean:webHandler和requestMappingHandlerMapping(實際上還注入了其它若干個Bean,不過我們暫時只關注這兩個吧)。

名爲"webHandler"的Bean的類型是DispatcherHandler,顧名思義是分發器,分發請求給各個處理單元。
名爲"requestMappingHandlerMapping"的Bean的類型是RequestMappingHandlerMapping,它的根本是實現了HandlerMapping接口。HandlerMapping的作用是將請求映射到Handler上,比如把某個請求路徑映射到某個Controller上。

關於DispatcherHandler和HandlerMapping,後面章節繼續深入講解。

public class WebFluxConfigurationSupport implements ApplicationContextAware {
    @Bean
    public DispatcherHandler webHandler() {
        return new DispatcherHandler();
    }
    
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
        mapping.setContentTypeResolver(webFluxContentTypeResolver());
        mapping.setCorsConfigurations(getCorsConfigurations());

        PathMatchConfigurer configurer = getPathMatchConfigurer();
        Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
        if (useTrailingSlashMatch != null) {
            mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
        }
        Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
        if (useCaseSensitiveMatch != null) {
            mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
        }
        Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
        if (pathPrefixes != null) {
            mapping.setPathPrefixes(pathPrefixes);
        }

        return mapping;
    }
}

2、DispatcherHandler

DispatcherHandler將自身作爲Bean註冊到Spring的Bean容器中,它與WebFilter、WebExceptionHandler等一起,組成處理鏈。

DispatcherHandler會從Spring Configuration中探尋到它需要的組件,主要是以下三類:

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    ...
    private List<HandlerMapping> handlerMappings;
    private List<HandlerAdapter> handlerAdapters;
    private List<HandlerResultHandler> resultHandlers;
    ...
}
  • HandlerMapping

    • 映射Request到Handler
    • HandlerMapping的默認實現是RequestMappingHandlerMapping,用於@RequestMapping所標記的方法;RouterFunctionMapping用於函數式端點的路由;SimpleUrlHandlerMapping用於顯式註冊的URL模式與WebHandler
  • HandlerAdaptor

    • 幫助DispatcherHandler調用Request所映射的Handler
    • HandlerAdaptor的主要作用是將DispatcherHandler從調用具體Handler的細節中解放出來
  • HandlerResultHandler

    • 處理Handler的結果並終結Response
    • 調用具體的Handler的返回結果是包裝在HandlerResult中的,HandlerResultHandler負責處理HandlerResult。比如ResponseBodyResultHandler負責處理@ResponseBody標記的方法的返回值

示意圖如下:

DispatcherHandler
  |
  |-->HandlerMapping
  |-->HandlerAdaptor
  |-->HandlerResultHandler

DispatcherHandler的核心方法是:

@Override
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));
}

該方法道出了DispatcherHandler處理請求的套路

  • 先通過HandlerMapping找到Request的專用Handler,具體的代碼片段是
concatMap(mapping -> mapping.getHandler(exchange))
  • 再通過HandlerAdaptor執行這個具體的Handler,具體的代碼片段是
flatMap(handler -> invokeHandler(exchange, handler))
  • 最後通過HandlerResultHandler處理Handler返回的結果,具體的代碼片段是
flatMap(result -> handleResult(exchange, result))

可能你有疑問,這個方法裏Request在哪呢?藏在"ServerWebExchange exchange"裏了,關於ServerWebExchange又其它文章進一步介紹,這兒就不作詳細說明了。

3、HandlerMapping

DispatcherHandler套路中,處理Request的第一環節就是使用HandlerMapping。常見的HandlerMapping有三種:RequestMappingHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping

RequestMappingHandlerMapping是默認的,是否還記得DispatcherHandler中有發佈一個Bean名爲"requestMappingHandlerMapping"?它承擔了映射Request與annotation-based Handler之間的關係。

由於我們習慣於使用@Controller、@RestController、@RequestMapping之類的註解來開發WEB項目,所以非常依賴RequestMappingHandlerMapping。我們接下來就談談RequestMappingHandlerMapping

3-1、RequestMappingHandlerMapping的類層級

1層:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware

  ^
  |

2層:AbstractHandlerMethodMapping implements InitializingBean

  ^
  |

3層:RequestMappingInfoHandlerMapping

  ^
  |

4層:RequestMappingHandlerMapping implements EmbeddedValueResolverAware

3-2、掃描可用handler

大家注意到,第2層實現了InitializingBean接口,實現了該接口的類有機會在BeanFactory設置好它的所有屬性後通過調用

void afterPropertiesSet()

方法通知它。我們來看看第2層的對該方法的實現吧。

@Override
public void afterPropertiesSet() {

    initHandlerMethods();
    
    // Total includes detected mappings + explicit registrations via registerMapping..
    ...
}

篇幅原因,這兒不細究方法中所調用的

initHandlerMethods();

的細節了 —— 實際上這裏面是掃描所有@Controller註解的類中的@RequestMapping及其變體所修飾的方法,即最終會處理Request的Handler,並緩存起來,以便後續進一步執行。

3-3、與DispatcherHandler相互配合

DispatcherHandler會調用MappingHandler的

Mono<Object> getHandler(ServerWebExchange exchange)

方法,選擇處理這個請求交的具體的Handler。

四、總結

Spring WebFlux模型的使用非常簡單,尤其是對於熟悉Spring MVC模型的開發人員來說,無縫切換。使用Spring Boot框架開發時,使用@Controller、@RestController、@RequestMapping等註解,實現處理Request的Handler尤其方便。

原文:http://www.yesdata.net/2018/1...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章