一、前言
本文主要介紹基於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形成自己的判斷標準。
文章系列
- 關於非阻塞IO:《從時間碎片角度理解阻塞IO模型及非阻塞模型》
- 關於SpringFlux新手入門:《快速上手Spring Flux框架》
- 爲什麼Spring要引入SpringFlux框架 尚未完成
- Spring Flux中Request與HandlerMapping關係的形成過程 尚未完成
- Spring Flux中執行HandlerMapping的過程 尚未完成
- Spring Flux中是如何處理HandlerResult的 尚未完成
- Spring Flux與WEB服務器之Servlet 3.1+ 尚未完成
- Spring Flux與WEB服務器之Netty 尚未完成
<!-- 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等)。
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"字樣。
怎麼樣,是不是很簡單。
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尤其方便。