文章目錄
- 前言
- 第13章 SpringBoot整合WebFlux
- 13.1 響應式編程與Reactor
- 13.1.1 命令式與響應式
- 13.1.2 異步非阻塞
- 13.1.3 觀察者模式
- 13.1.4 響應性
- 13.1.5 響應式流
- 13.1.6 背壓
- 13.1.7 Reactor
- 13.1.7.1 Publisher
- 13.1.7.2 Subscriber
- 13.1.7.3 Subscription
- 13.1.7.4 Processor
- 13.1.7.5 Flux
- 13.1.7.6 Mono
- 13.1.7.7 Scheduler
- 13.2 SpringBoot整合WebFlux示例項目
- 13.2.1 WebMvc的開發風格
- 13.2.2 過渡到WebFlux
- 13.2.3 WebFlux的函數式開發
- 12.2.3.1 Controller轉Handler
- 12.2.3.2 RequestMapping轉Router
- 13.2.4 WebMvc和WebFlux的對比
- 13.3 WebFlux的自動裝配
- 13.3.1 ReactiveWebServerFactoryAutoConfiguration
- 13.3.2 WebFluxAutoConfiguration
- 13.3.3 WebFluxConfig
- 13.3.3.1 靜態資源映射
- 13.3.3.2 視圖解析器
- 13.3.4 EnableWebFluxConfiguration
- 13.3.5 WebFluxConfigurationSupport
- 13.3.5.1 DispatcherHandler
- 13.3.5.2 WebExceptionHandler
- 13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
- 13.3.5.4 RouterFunctionMapping
- 13.3.5.5 HandlerFunctionAdapter
- 13.3.5.6 ResultHandler
前言
SpringFramework 5.x中對于Web場景的開發提供了兩套實現方案:WebMvc與WebFlux。
SpringBoot整合WebMvc基于Servlet,本質上是阻塞的,每個連接都會占用一個線程,因此基于Servlet的阻塞式Web框架在面對海量請求時,性能上沒有優勢。
為了解決該問題,SpringFramework 5.0版本后引入了WebMvc的孿生兄弟WebFlux,它是一個異步非阻塞式Web框架。
第13章 SpringBoot整合WebFlux
13.1 響應式編程與Reactor
13.1.1 命令式與響應式
- 命令式編程
在基于WebMvc的項目開發中,通過編寫Controller前端控制器,注入Service業務邏輯類進行處理,Service中包含與數據庫的交互、與中間件的通信等,這種編碼風格就是命令式編程。
使用命令式編程的代碼,就像是一組前后緊密聯系的任務,有明確的先后執行順序,后面的任務通常需要依賴前面的任務生成的結果才能正確執行。
命令式編程的特點是串行、阻塞。
- 響應式編程
響應式編程不再將這些緊密聯系的任務看作一個整體,而是將其拆分為一個個可以并行執行的工作任務,這些任務之間互不干擾。
每個工作任務都可以接收特定的數據,并在處理完成后傳遞給下一個任務,同時繼續處理下一組數據。
在響應式編程中,每個任務不會主動獲取數據,而是被動地等待數據提供方給它提供數據,即主張數據以訂閱的方式推送(被動接收),而不是以請求的方式拉取(主動獲取)。
13.1.2 異步非阻塞
使用一個非常經典的故事來解釋異步非阻塞。
假設有一個老張燒水的場景,老張有兩把燒水壺,分別是沒有哨的普通水壺以及壺蓋上帶哨的響水壺。燒水的場景包含以下4種:
- 同步阻塞式:使用普通水壺燒水,由于不清楚水燒開的時間,因此需要老張在水壺旁觀察,等到水壺冒熱氣,壺里的水沸騰,老張將水壺離火,燒水結束。在該場景中,由于老張在燒水期間無法完成其他工作,只能等待水燒開,燒水占據了老張的注意力和時間,構成同步阻塞。
- 同步非阻塞:經過上一次燒水后,老張發現燒水太浪費自己的時間,于是下一次燒水時老張選擇同時打游戲,每隔一小段時間就去看一下水壺里的水是否燒開,如果水還沒有燒開就繼續打游戲,水燒開則將水壺離火,燒水結束。在該場景中,老張沒有一直盯著水壺,但還是會間歇性消耗精力,只不過在整個燒水的過程中,老張沒有一直被水壺占用全部精力和時間,構成同步非阻塞。
- 異步阻塞式:間歇性觀察水壺仍然不是最佳選擇,老張選擇使用響水壺燒水,但由于第一次使用響水壺燒水,老張不確定水壺上的哨是否好用,于是他像第一次燒水那樣在水壺旁觀察,等到水壺冒熱氣,同時哨聲響起,老張將水壺離火,燒水結束。在該場景中老張不再主動關心水壺的狀態,但精力和時間仍然被水壺占用,構成異步阻塞。
- 異步非阻塞:燒完水后老張發現自己很傻,因為哨聲響起就意味著水已燒開,無須自己消耗精力和時間,于是后續燒水時,老張都是準備好后直接去打游戲,等到水壺哨聲響起,再將水壺離火,燒水結束。在最終的場景中,老張不再主動關心水壺狀態,也不需要間歇性檢查水壺內水的狀態,而只需要在水壺的哨聲響起時處理水壺離火的任務,此場景就是異步非阻塞。
13.1.3 觀察者模式
觀察者模式也被稱為“發布——訂閱者模式”或“監聽器模式”。當一個對象被修改/做出某些反應/發布一個信息時,會自動通知依賴它的對象(訂閱者)。
觀察者模式的三大核心是觀察者、被觀察的主題和訂閱者。觀察者(Observer)需要綁定訂閱者(Subscriber),并且要觀察指定的主題(Subject)。
13.1.4 響應性
如上圖,在Excel表格中,A2單元格的值是通過公式=A0+A1
來定義的,因此最終得到的值是2。如果試著更改A0或A1單元格的值,A2單元格的值也會自動更新。這就是響應性的體現。
- A2單元格像是在“觀察”著A0和A1單元格中輸入的值,當A0或A1單元格中輸入的值發生變化時,A2單元格的值也會隨之變化,這本身就是觀察者模式的體現。由此引出響應式編程的第一個關鍵概念:變化傳遞。即A0或A1單元格中輸入的值發生變化時,這些變化的值會傳遞到A2單元格中。
- 在實際使用中,每修改一次A0或A1單元格的值,A2單元格的值就會隨之變化,如果將這組變化的內容全部列舉,可以形成一組單元格內容的變化事件記錄。由此可以引出響應式編程的第二個關鍵概念:數據流。事件源的每一次變化連起來就是一個事件流。
- 在上面的Excel示例中,僅僅通過一個公式就將綁定了A2單元格和A0、A1單元格的關系。由此可以引出響應式編程的第三個關鍵概念:聲明式。不需要編寫命令式代碼,僅靠聲明兩者之間的關系就可以形成雙向綁定。
簡單總結,響應式編程的三個關鍵點是變化傳遞、數據流和聲明式。
13.1.5 響應式流
響應式流有別于Java8中的Stream。普通的Stream是同步阻塞的,在高并發場景下不能有效緩解壓力大的問題,而響應式流是異步非阻塞的。
普通的Stream的一個關鍵特性是,一旦有了消費型方法,它就會將這個流中的所有數據處理完畢,如果這期間的數據量很大,Stream就無法對海量數據進行妥善處理;而響應式流可以通過背壓對海量數據進行流量控制,以確保數據的接收速度和處理在合理范圍內。
簡單總結,響應式流的關鍵點是異步非阻塞和數據流速控制。
13.1.6 背壓
**背壓是控制數據流速的關鍵手段。**下面以一個模擬場景來解釋:
假設你在一個知名手機生產大廠工作,你的職位是生產流水線上的一名普通工人,你的工作是負責流水線上的一個關鍵環節。該環節需要的加工時間比較長,而恰好近期與你共同負責相同工作的同事都請假了,剩下你單槍匹馬仍然戰斗在生產一線。
與此同時,負責你上游工作的同事似乎并不清楚你負責環節的生產現狀,而且由于上司的激勵政策,上游同事的生產效率非常高,導致你的待加工區積壓了非常多半成品,但由于你負責的工序耗時長,積壓的半成品過多無法及時處理,于是你不得不向上游同事反饋:你們做慢點,我的工作吞吐量有限。上游同事了解你的現狀后改變了半成品處理策略,他們將處理好的半成品不直接傳遞給你,而暫時由上游同事保管,等你向他們反饋積壓的半成品處理完畢后,再繼續傳遞新的半成品。
由此可以體現背壓的第一個策略:數據提供方將數據暫存,不傳遞給下游消費者。
一段時間之后,領導發現你的業績非常好,于是你升職加薪,以經銷商的身份銷售該款手機。手機一上市就得到廣大消費者的關注,你的店鋪生意非常好。正當你的生意做得風生水起時,這批手機在售賣后的一段時間后傳出硬件問題,市面銷量急劇下降,作為經銷商,你自然也不想再銷售該款手機,于是你向廠商反映:請不要再提供該款手機。廠商也非常無奈,手機還在正常生產,但經銷商都不再提貨,于是只好將這部分成品廢棄。
由此可以體現出背壓的第二個策略:數據提供方將數據丟棄。
簡單總結,背壓是下游消費者“倒逼”上游數據生產者的數據提供速率,以避免被海量數據壓垮,達到兩者之間的動態平衡。
13.1.7 Reactor
市面上流行的響應式編程框架包括Reactor與ReactiveX(RxJava)。WebFlux底層使用Reactor提供響應式支撐。
Reactor的核心組件如下:
13.1.7.1 Publisher
源碼1:Publisher.javapublic interface Publisher<T> {public void subscribe(Subscriber<? super T> s);
}
由 源碼1 可知,數據生產者Publisher只有一個方法subscribe
,該方法會接收一個訂閱者Subscriber,構成“訂閱”關系。
注意,subscribe
方法是一個類似于“工廠”的方法,它可以被多次調用,但是每次調用都會創建一個新的訂閱關系,且一個訂閱關系只能關聯一個訂閱者。
13.1.7.2 Subscriber
源碼2:Subscriber.javapublic interface Subscriber<T> {public void onSubscribe(Subscription s);public void onNext(T t);public void onError(Throwable t);public void onComplete();
}
由 源碼2 可知,數據訂閱者Subscriber接口有4個方法,都是以on作為前綴,代表這些方法屬于事件形式。
- onSubscribe:當觸發訂閱時觸發;
- onNext:當接收到下一個數據時觸發;
- onError:當出現異常時觸發;
- onComplete:當生產者的數據都處理完時觸發。
13.1.7.3 Subscription
源碼3:Subscription.javapublic interface Subscription {public void request(long n);public void cancel();
}
Subscription可以看作是生產者和訂閱者之間的訂閱關系,完成了兩者之間的交互。由 源碼2 可知,訂閱關系Subscription接口有2個方法:
- request:用于主動請求數據/拉取數據;
- cancel:用于放棄/停止拉取數據。
13.1.7.4 Processor
源碼4:Processor.javapublic interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Processor可以理解為處理器,一般用于數據的中間環節處理(如數據轉換、數據過濾等)。由 源碼4 可知,Processor接口繼承了Subscriber接口和Publisher接口,是生產者和訂閱者的合體。
13.1.7.5 Flux
源碼5:Flux.javapublic abstract class Flux<T> implements CorePublisher<T> {...}
**Flux可以理解為“非阻塞的Stream”。**由 源碼5 和上圖可知,它實現了Publisher接口,內部定義了很多subscribe
方法。重載這么多個subscribe
方法的目的在于簡化操作。
13.1.7.6 Mono
源碼6:Mono.javapublic abstract class Mono<T> implements CorePublisher<T> {...}
**Flux可以理解為“非阻塞的Optional”。**它也實現了Publisher接口,具備生產數據的能力,內部API和Flux相似。
13.1.7.7 Scheduler
Scheduler可以理解為“線程池”,由Schedulers工具類產生。
響應式線程池有以下幾種類型:
- immediate:與主線程一致。
- single:只有一個線程的線程池。
- elastic:彈性線程池,線程池中的線程數量原則上無上限。
- parallel:并性線程池,線程池中的線程數量等于CPU的數量(JDK中的Runtime類可以調用
avaliableProcessors
方法來獲取CPU梳理)。
13.2 SpringBoot整合WebFlux示例項目
WebMvc和WebFlux是地位同等的框架,因此SpringBoot為了避免開發者因WebFlux的使用門檻過高而放棄,在WebFlux的使用過程中允許采用WebMvc的開發風格,即使用@Controller+@RequestMapping注解組合實現基于WebFlux的前端控制和響應。
13.2.1 WebMvc的開發風格
- 導入WebFlux依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- 編寫主啟動類
@SpringBootApplication
public class WebFluxApp {public static void main(String[] args) {SpringApplication.run(WebFluxApp.class, args);}
}
這時就可以啟動項目了,啟動后控制臺輸出以下信息:
Starting WebFluxApp on DESKTOP-VTHK7VU with PID 14732 (D:\learnspace\workspace\java_src\springboot-demo\springboot-08-webflux\target\classes started by win10 in D:\learnspace\workspace\java_src\springboot-demo)
No active profile set, falling back to default profiles: default
Netty started on port(s): 8080
Started WebFluxApp in 3.657 seconds (JVM running for 5.44)
可以發現,項目啟動的嵌入式Web容器不再是Tomcat,而是Netty。
- 編寫Controller類
@RestController
public class UserController {@GetMapping("/hello")public String hello() {return "Hello WebFlux!";}@GetMapping("/list")public List<Integer> list() {return Arrays.asList(1, 2, 3);}}
編寫完成后啟動項目,使用工具訪問 http://127.0.0.1:8080/hello
和 /list
,客戶端可以正常接收到服務端的 “Hello WebFlux!” 字符串響應,說明WebFlux可以完美兼容WebMvc的編碼方式。
13.2.2 過渡到WebFlux
Reactor中核心數據的封裝模型是Mono和Flux,下面使用這兩個模型對UserController進行改造。當返回單個對象時,使用Mono封裝;當返回一組數據時,使用Flux封裝。
@RestController
public class UserController {@GetMapping("/hello2")public Mono<String> hello2() {return Mono.just("Hello WebFlux!");}@GetMapping("/list2")public Flux<Integer> list2() {return Flux.just(1, 2, 3);}}
重新啟動項目,并訪問 http://127.0.0.1:8080/hello2
和 /list2
,客戶端仍然可以正常接收到服務端響應的正常數據,說明Reactor中的數據模型作為響應主體完全可行。
13.2.3 WebFlux的函數式開發
如果要完全丟棄WebMvc的編碼風格,則需要使用WebFlux提供的一套全新的函數式API。
在WebMvc中,一個Controller類中標注了@RequestMapping注解的方法在底層會封裝為一個個Handler,每個Handler都封裝有URL+執行方法以及具體要反射執行的Method對象。這兩個核心要素在WebFlux的編碼風格中會轉換為兩個核心組件:HandlerFunction和RouterFunction。
12.2.3.1 Controller轉Handler
WebFlux的編碼風格不再使用@Controller注解,而是使用原始的@Component注解,且內部的方法不再需要多余的注解,只需要按照WebFlux的規則編寫方法。
@Component
public class UserHandler {public Mono<ServerResponse> hello3(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(Mono.just("Hello Handler"), String.class);}public Mono<ServerResponse> list3(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(Flux.just(1, 2, 3), Integer.class);}
}
12.2.3.2 RequestMapping轉Router
由于UserHandler中不再有@Controller注解,因此方法上也不再使用@RequestMapping注解封裝URL信息,因此Spring無法感知IOC容器中哪些bean對象具備WebFlux前端控制器的能力,這就需要一個新的組件來定義Bean與具體路由的關系,這個組件就是RouterFunction。
在編寫具體的路由規則時,需要一個配置類來編程式創建RouterFunction對象:
@Configuration(proxyBeanMethods = false)
public class UserRouterConfig {@Autowiredprivate UserHandler userHandler;@Beanpublic RouterFunction<ServerResponse> helloRouter() {return RouterFunctions.route(GET("/hello3").and(accept(MediaType.TEXT_PLAIN)), userHandler::hello3).andRoute(GET("/list3").and(accept(MediaType.APPLICATION_JSON)), userHandler::list3);}
}
至此,一個基于WebFlux編碼風格的示例項目搭建完畢。啟動項目,并訪問 http://127.0.0.1:8080/hello3
和 /list3
,客戶端仍然可以正常接收到服務端響應的正常數據,說明基于WebFlux編碼風格的示例項目搭建成功。
13.2.4 WebMvc和WebFlux的對比
- WebMvc基于原生Servlet,它是命令式編程+聲明式映射,編碼簡單、便于調試;Servlet是阻塞的,更適合與傳統關系型數據庫等阻塞I/O的組件進行交互。
- WebFlux基于Reactor,它是異步非阻塞的,使用函數式編程,相較于命令式編程更加靈活,可以運行在Netty等純異步非阻塞的Web容器,以及同時支持同步阻塞和異步非阻塞的基于Servlet 3.1及以上規范的Servlet容器中(如高版本的Tomcat等)。
- WebMvc和WebFlux都可以使用聲明式映射注解編程,配置控制器和映射路徑。
在實際的項目技術選型中,需要綜合考慮項目中使用的技術棧、用戶群規模、開發團隊能力等多方面因素,決定是采用WebMvc還是WebFlux。
13.3 WebFlux的自動裝配
WebFlux的自動裝配類似于WebMvc,對應的自動配置類是ReactiveWebServerFactoryAutoConfiguration和WebFluxAutoConfiguration。
13.3.1 ReactiveWebServerFactoryAutoConfiguration
源碼7:ReactiveWebServerFactoryAutoConfiguration.java@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ReactiveHttpInputMessage.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class })
public class ReactiveWebServerFactoryAutoConfiguration
由 源碼7 可知,該自動配置類使用@Import注解導入的核心配置類是BeanPostProcessorsRegistrar和幾個嵌入式Web容器類。
與WebMvc的自動配置類ServletWebServerFactoryAutoConfiguration相比,導入的嵌入式Web容器多了一個Netty。
源碼8:ReactiveWebServerFactoryConfiguration.java@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
@ConditionalOnClass({HttpServer.class})
static class EmbeddedNetty {@Bean@ConditionalOnMissingBeanReactorResourceFactory reactorServerResourceFactory() {return new ReactorResourceFactory();}@BeanNettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory,ObjectProvider<NettyRouteProvider> routes, ObjectProvider<NettyServerCustomizer> serverCustomizers) {NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();serverFactory.setResourceFactory(resourceFactory);routes.orderedStream().forEach(serverFactory::addRouteProviders);serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));return serverFactory;}}
由 源碼8 可知,EmbeddedNetty中注冊的Bean包括NettyReactiveWebServerFactory和ReactorResourceFactory。
NettyReactiveWebServerFactory會在IOC容器的初始化階段創建嵌入式Netty容器。
ReactorResourceFactory是一個可以管理Reactor Netty資源的工廠,這個設計類似于線程池。
源碼9:ReactorResourceFactory.javapublic class ReactorResourceFactory implements InitializingBean, DisposableBean {// ......private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.fixed("webflux", 500);//......
}
源碼10:ConnectionProvider.javastatic ConnectionProvider fixed(String name, int maxConnections) {return fixed(name, maxConnections, DEFAULT_POOL_ACQUIRE_TIMEOUT);
}
static ConnectionProvider fixed(String name, int maxConnections, long acquireTimeout) {return fixed(name, maxConnections, acquireTimeout, null, null);
}
static ConnectionProvider fixed(String name, int maxConnections, long acquireTimeout, @Nullable Duration maxIdleTime, @Nullable Duration maxLifeTime) {// ......return builder(name).maxConnections(maxConnections).pendingAcquireMaxCount(-1) // keep the backwards compatibility.pendingAcquireTimeout(Duration.ofMillis(acquireTimeout)).maxIdleTime(maxIdleTime).maxLifeTime(maxLifeTime).build();
}
public ConnectionProvider build() {return new PooledConnectionProvider(this);
}
由 源碼9-10 可知,ReactorResourceFactory內部組合了一個ConnectionProvider,它會初始化一個最大連接數為500的連接池,其落地實現類為PooledConnectionProvider。由此可以理解ReactorResourceFactory就是一個Reactor Netty的連接池。
13.3.2 WebFluxAutoConfiguration
源碼11:WebFluxAutoConfiguration.java@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class, CodecsAutoConfiguration.class,ValidationAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration
由 源碼11 可知,WebFluxAutoConfiguration生效的前提是當前項目的Web類型為REACTIVE(@ConditionalOnWebApplication注解),以及需要當前項目類路徑下存在WebFluxConfigurer類(@ConditionalOnClass注解)。
在WebFluxAutoConfiguration的內部,有幾個靜態內部類根據不同功能和場景分別配置對應的組件。
13.3.3 WebFluxConfig
源碼12:WebFluxAutoConfiguration.java@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import({ EnableWebFluxConfiguration.class })
public static class WebFluxConfig implements WebFluxConfigurer {...}
由 源碼12 可知,WebFluxConfig類使用@Import注解導入了EnableWebFluxConfiguration。
WebFluxConfig類本身實現了WebFluxConfigurer接口,因此具備配置WebFlux的能力。
13.3.3.1 靜態資源映射
源碼13:WebFluxAutoConfiguration.javapublic static class WebFluxConfig implements WebFluxConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 前置檢查 ......if (!registry.hasMappingForPattern("/webjars/**")) {ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");// ......}String staticPathPattern = this.webFluxProperties.getStaticPathPattern();if (!registry.hasMappingForPattern(staticPathPattern)) {ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern).addResourceLocations(this.resourceProperties.getStaticLocations());// ......}}
}
源碼14:ResourceProperties.javaprivate static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// ......
由 源碼13-14 可知,```addResourceHandlers``方法會默認配置幾個常用的約定好的靜態文件的存放位置:/resources、/static、/public、/webjars等等。這些路徑下的靜態文件是可以被直接引用的。
13.3.3.2 視圖解析器
源碼15:WebFluxAutoConfiguration.javapublic static class WebFluxConfig implements WebFluxConfigurer {@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {this.viewResolvers.orderedStream().forEach(registry::viewResolver);}
}
由 源碼15 可知,WebFlux也支持視圖跳轉,底層也有視圖解析器的配置。
13.3.4 EnableWebFluxConfiguration
EnableWebFluxConfiguration與WebMvc中的EnableWebMvcConfiguration相似。
源碼16:WebFluxAutoConfiguration.java@Configuration(proxyBeanMethods = false)
public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration {...}
由 源碼16 可知,EnableWebFluxConfiguration繼承了父類DelegatingWebFluxConfiguration。
EnableWebFluxConfiguration配置類種注冊的組件包括:
- FormattingConversionService:參數類型轉換器。用于數據的類型轉換,如日期與字符串之間的互相轉換。
- Validator:JSR-303參數校驗器。
- RequestMappingHandlerAdapter:標注了@RequestMapping注解的Handler的執行器。
- RequestMappingHandlerMapping:標注了@RequestMapping注解的Handler的處理器。
13.3.5 WebFluxConfigurationSupport
EnableWebFluxConfiguration繼承父類DelegatingWebFluxConfiguration,而DelegatingWebFluxConfiguration又集成父類WebFluxConfigurationSupport。
WebFluxConfigurationSupport類中也有一些核心組件的注冊。
13.3.5.1 DispatcherHandler
源碼18:WebFluxConfigurationSupport.java@Bean
public DispatcherHandler webHandler() {return new DispatcherHandler();
}
WebFlux中的核心前端控制器是DispatcherHandler,對應WebMvc中的DispatcherServlet。由 源碼18 可知,DispatcherHandler組件的注冊僅僅是創建一個新對象。
13.3.5.2 WebExceptionHandler
源碼19:WebFluxConfigurationSupport.java@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {return new WebFluxResponseStatusExceptionHandler();
}
WebFlux中的異常狀態響應器用于處理異常情況下的HTTP狀態碼響應,如 源碼19 所示,其實現類是WebFluxResponseStatusExceptionHandler。
13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
源碼20:WebFluxConfigurationSupport.java@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();mapping.setOrder(0);// ......return mapping;
}@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,ServerCodecConfigurer serverCodecConfigurer,@Qualifier("webFluxConversionService") FormattingConversionService conversionService,@Qualifier("webFluxValidator") Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();// ......return adapter;
}
因為WebFlux可以完美支持WebMvc中使用@RequestMapping注解的方式定義HAndler,支持這種方式的底層組件就是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
13.3.5.4 RouterFunctionMapping
源碼21:WebFluxConfigurationSupport.java@Bean
public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) {RouterFunctionMapping mapping = createRouterFunctionMapping();mapping.setOrder(-1); // 此處設置優先級高于RequestMappingHandlerMappingmapping.setMessageReaders(serverCodecConfigurer.getReaders());mapping.setCorsConfigurations(getCorsConfigurations());return mapping;
}
RouterFunctionMapping是基于函數式端點路由編程的Mapping處理器。由 源碼21 可知,它的優先級高于RequestMappingHandlerMapping,這意味著WebFlux傾向于開發中使用函數式端點的Web開發,而不是傳統的@RequestMapping注解式開發。
13.3.5.5 HandlerFunctionAdapter
源碼22:WebFluxConfigurationSupport.java@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {return new HandlerFunctionAdapter();
}
HandlerFunctionAdapter是Handler方法的執行器。由 源碼22 可知,對應的支撐組件是HandlerFunctionAdapter,它可以直接提取出HandlerFunction中的Handler方法進行調用。
13.3.5.6 ResultHandler
ResultHandler是WebFlux中對返回值進行處理的組件,對應到WebMvc中則是HandlerMethodReturnValueHadnler。
默認情況下,WebFlux會注冊4種不同的ResultHandler實現類。
- ResponseEntityResultHandler:處理HttpEntity和ResponseEntity。
源碼23:WebFluxConfigurationSupport.java@Bean
public ResponseEntityResultHandler responseEntityResultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,ServerCodecConfigurer serverCodecConfigurer,@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(),contentTypeResolver, reactiveAdapterRegistry);
}
- ResponseBodyResultHandler:處理@RequestMapping的標注了@ResponseBody注解的Handler。
源碼24:WebFluxConfigurationSupport.java@Bean
public ResponseBodyResultHandler responseBodyResultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,ServerCodecConfigurer serverCodecConfigurer,@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),contentTypeResolver, reactiveAdapterRegistry);
}
- ViewResolutionResultHandler:處理邏輯視圖返回值。
源碼25:WebFluxConfigurationSupport.java@Bean
public ViewResolutionResultHandler viewResolutionResultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {ViewResolverRegistry registry = getViewResolverRegistry();List<ViewResolver> resolvers = registry.getViewResolvers();ViewResolutionResultHandler handler = new ViewResolutionResultHandler(resolvers, contentTypeResolver, reactiveAdapterRegistry);handler.setDefaultViews(registry.getDefaultViews());handler.setOrder(registry.getOrder());return handler;
}
- ServerResponseResultHandler:處理返回值類型為ServerResponse的。
源碼26:WebFluxConfigurationSupport.java@Bean
public ServerResponseResultHandler serverResponseResultHandler(ServerCodecConfigurer serverCodecConfigurer) {List<ViewResolver> resolvers = getViewResolverRegistry().getViewResolvers();ServerResponseResultHandler handler = new ServerResponseResultHandler();handler.setMessageWriters(serverCodecConfigurer.getWriters());handler.setViewResolvers(resolvers);return handler;
}
······
本節完,更多內容請查閱分類專欄:SpringBoot源碼解讀與原理分析