SpringBoot源碼解讀與原理分析(三十八)SpringBoot整合WebFlux(一)WebFlux的自動裝配

文章目錄

  • 前言
  • 第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中的響應性

如上圖,在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
源碼1Publisher.javapublic interface Publisher<T> {public void subscribe(Subscriber<? super T> s);
}

由 源碼1 可知,數據生產者Publisher只有一個方法subscribe,該方法會接收一個訂閱者Subscriber,構成“訂閱”關系。

注意,subscribe方法是一個類似于“工廠”的方法,它可以被多次調用,但是每次調用都會創建一個新的訂閱關系,且一個訂閱關系只能關聯一個訂閱者。

13.1.7.2 Subscriber
源碼2Subscriber.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
源碼3Subscription.javapublic interface Subscription {public void request(long n);public void cancel();
}

Subscription可以看作是生產者和訂閱者之間的訂閱關系,完成了兩者之間的交互。由 源碼2 可知,訂閱關系Subscription接口有2個方法:

  • request:用于主動請求數據/拉取數據;
  • cancel:用于放棄/停止拉取數據。
13.1.7.4 Processor
源碼4Processor.javapublic interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

Processor可以理解為處理器,一般用于數據的中間環節處理(如數據轉換、數據過濾等)。由 源碼4 可知,Processor接口繼承了Subscriber接口和Publisher接口,是生產者和訂閱者的合體。

13.1.7.5 Flux
源碼5Flux.javapublic abstract class Flux<T> implements CorePublisher<T> {...}

Flux定義了很多subscribe方法

**Flux可以理解為“非阻塞的Stream”。**由 源碼5 和上圖可知,它實現了Publisher接口,內部定義了很多subscribe方法。重載這么多個subscribe方法的目的在于簡化操作。

13.1.7.6 Mono
源碼6Mono.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的編碼方式

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中的數據模型作為響應主體完全可行。

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編碼風格的示例項目搭建成功。

基于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

源碼7ReactiveWebServerFactoryAutoConfiguration.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。

源碼8ReactiveWebServerFactoryConfiguration.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資源的工廠,這個設計類似于線程池。

源碼9ReactorResourceFactory.javapublic class ReactorResourceFactory implements InitializingBean, DisposableBean {// ......private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.fixed("webflux", 500);//......
}
源碼10ConnectionProvider.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

源碼11WebFluxAutoConfiguration.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

源碼12WebFluxAutoConfiguration.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 靜態資源映射
源碼13WebFluxAutoConfiguration.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());// ......}}
}
源碼14ResourceProperties.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 視圖解析器
源碼15WebFluxAutoConfiguration.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相似。

源碼16WebFluxAutoConfiguration.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
源碼18WebFluxConfigurationSupport.java@Bean
public DispatcherHandler webHandler() {return new DispatcherHandler();
}

WebFlux中的核心前端控制器是DispatcherHandler,對應WebMvc中的DispatcherServlet。由 源碼18 可知,DispatcherHandler組件的注冊僅僅是創建一個新對象。

13.3.5.2 WebExceptionHandler
源碼19WebFluxConfigurationSupport.java@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {return new WebFluxResponseStatusExceptionHandler();
}

WebFlux中的異常狀態響應器用于處理異常情況下的HTTP狀態碼響應,如 源碼19 所示,其實現類是WebFluxResponseStatusExceptionHandler。

13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
源碼20WebFluxConfigurationSupport.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
源碼21WebFluxConfigurationSupport.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
源碼22WebFluxConfigurationSupport.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。
源碼23WebFluxConfigurationSupport.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。
源碼24WebFluxConfigurationSupport.java@Bean
public ResponseBodyResultHandler responseBodyResultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,ServerCodecConfigurer serverCodecConfigurer,@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),contentTypeResolver, reactiveAdapterRegistry);
}
  • ViewResolutionResultHandler:處理邏輯視圖返回值。
源碼25WebFluxConfigurationSupport.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的。
源碼26WebFluxConfigurationSupport.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源碼解讀與原理分析

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713687.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713687.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713687.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

BF算法實現(Python,C++)

BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是將目標串S的第一個字符與模式串T的第一個字符進行匹配&#xff0c;若相等&#xff0c;則繼續比較S的第二個字符和 T的第二個字符&#xff1b;若不相等&#xff0c;則比…

Leetcoder Day32| 貪心算法part05

763.劃分字母區間 字符串 S 由小寫字母組成。我們要把這個字符串劃分為盡可能多的片段&#xff0c;同一字母最多出現在一個片段中。返回一個表示每個字符串片段的長度的列表。 示例&#xff1a; 輸入&#xff1a;S "ababcbacadefegdehijhklij"輸出&#xff1a;[9,7…

今日早報 每日精選15條新聞簡報 每天一分鐘 知曉天下事 3月2日,星期六

每天一分鐘&#xff0c;知曉天下事&#xff01; 2024年3月2日 星期六 農歷正月廿二 1、 氣象局&#xff1a;3月份仍有5次冷空氣影響我國&#xff1b;全國多地或提前入春。 2、 央行&#xff1a;將外籍來華人員移動支付單筆交易限額由1000美元提高到5000美元。 3、 神舟十七號航…

全量知識系統問題及SmartChat給出的答復 之8 三套工具之3語法解析器 之1

Q19. 問題 : 解釋單詞解釋單詞occupied 的字典條目 (word-def occupiedinterest 5type EBsubclass SEBtemplate (script $Demonstrateactor nilobject nildemands nilmethod (scene $Occupyactor nillocation nil))fill (((actor) (top-of *actor-s…

【源碼】imx6ull實現觸摸屏單點實驗

一、本實驗實驗的器材&#xff1a; 1.正點原子imx6ull的阿爾法開發板v2.2 2.屏幕ALIENTEK 4.3 RGBLCD 二、實驗已經移植好的文件&#xff1a; 倉庫代碼&#xff1a;https://gitee.com/wangyoujie11/atkboard_-linux_-driver.git 1.文件說明 23_multitouch &#xff1a;驅動代…

aws平臺的ec2實例 GNU/Linux系統安裝docker流程

在AWS EC2實例上安裝Docker的流程與其他GNU/Linux系統基本相同。以下是在AWS EC2實例上安裝Docker的一般步驟&#xff1a; 登錄到AWS EC2實例&#xff1a; 使用SSH或者其他遠程登錄方式登錄到你的GNU/Linux實例。 更新系統包管理器&#xff1a; 對于基于Amazon Linux的系統&am…

常見Prometheus exporter部署

常見Prometheus exporter部署 Prometheus部署Node exporterProcess exporterRedis exporterMySQL exporterOracleDB exporter Prometheus部署 本地部署&#xff1a; wget https://github.com/prometheus/prometheus/releases/download/v*/prometheus-*.*-amd64.tar.gz tar xv…

java的jar打包docker鏡像,啟動加載

測試環境&#xff0c;打包鏡像 1,把jar包復制/data/liu/mssda.jar, cd到這個目錄下 2&#xff0c;創建Dockerfile文件&#xff0c;jdk17版本&#xff0c;內容如下 jdk8版本 FROM openjdk:8-jre-alpine WORKDIR /app COPY . /app CMD ["java", "-jar",…

最大奇約數(c++題解)

內存限制&#xff1a; 128 MiB時間限制&#xff1a; 100 ms標準輸入輸出題目類型&#xff1a; 傳統評測方式&#xff1a; 文本比較 題目描述 定義函數f(x)表示x的最大奇約數&#xff0c;這里x表示正整數。例如&#xff0c;f(20) 5&#xff0c;因為20的約數從小到大分別有&am…

奧地利羅馬尼亞媒體宣發稿對跨境出海推廣新聞營銷的意義

【本篇由言同數字科技有限公司原創】在當今全球化的時代&#xff0c;品牌跨境海外推廣已成為企業拓展國際市場的必要途徑。而奧地利和羅馬尼亞是歐洲重要的市場之一&#xff0c;通過在當地媒體上發表文章&#xff0c;可以幫助品牌成功打入這兩個市場&#xff0c;獲得更多的機會…

【YOLO v5 v7 v8 小目標改進】ODConv:在卷積核所有維度(數量、空間、輸入、輸出)上應用注意力機制來優化傳統動態卷積

ODConv&#xff1a;在卷積核所有維度&#xff08;數量、空間、輸入、輸出&#xff09;上應用注意力機制來優化傳統的動態卷積 提出背景傳統動態卷積全維動態卷積效果 小目標漲點YOLO v5 魔改YOLO v7 魔改YOLO v8 魔改 論文&#xff1a;https://openreview.net/pdf?idDmpCfq6Mg…

leedcode刷題--day7(字符串)

23 文章講解 力扣地址 C class Solution { public:void reverseString(vector<char>& s) {int left 0;int right s.size() - 1; // right 應該初始化為 s.size() - 1while (left < right) {swap(s[left], s[right]); // 直接交換 s[left] 和 s[right] 的值lef…

(學習日記)2024.02.29:UCOSIII第二節

寫在前面&#xff1a; 由于時間的不足與學習的碎片化&#xff0c;寫博客變得有些奢侈。 但是對于記錄學習&#xff08;忘了以后能快速復習&#xff09;的渴望一天天變得強烈。 既然如此 不如以天為單位&#xff0c;以時間為順序&#xff0c;僅僅將博客當做一個知識學習的目錄&a…

WSL2外部網絡設置

1 關閉所有WSL系統 wsl --shutdown 2 打開Hyper-V管理器 3 將“虛擬交換機管理器”-> ”WSL連接類型“設置為“外部網絡” 4 啟動WSL系統&#xff0c;手動修改WSL網絡 將WSL網絡IP修改為192.168.1.9 sudo ip addr del $(ip addr show eth0 | grep inet\b | awk {print $2} |…

FFmpeg+OpenCV開發案例匯總

桌面共享工具&#xff08;軟編版&#xff09; 桌面共享工具&#xff08;DXGI硬編版&#xff09; 智能廣告大屏&#xff08;可疊加透明廣告&#xff09; Android手機屏幕RTMP推流工具&#xff08;推麥克風版&#xff09; Android手機屏幕RTMP推流工具&#xff08;推揚聲器版…

FinalMLP:用于推薦系統的簡單但強大的雙流 MLP 模型

原文地址&#xff1a;FinalMLP: A Simple yet Powerful Two-Stream MLP Model for Recommendation Systems 了解 FinalMLP 如何轉變在線推薦&#xff1a;通過尖端 AI 研究解鎖個性化體驗 2024 年 2 月 14 日 介紹 世界正在向數字時代發展&#xff0c;在這個時代&#xff0c;…

Python并發編程:多線程-死鎖現象與遞歸鎖

一  死鎖現象 所謂死鎖&#xff1a;是指兩個或兩個以上的進程或線程在執行過程中&#xff0c;因爭奪資源而造成的一種互相等待的現象&#xff0c;若無外力作用&#xff0c;它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖&#xff0c;這些永遠在互相等待的進程…

持安科技孫維伯:零信任在攻防演練下的最佳實踐|DISCConf 2023

近日&#xff0c;在2023數字身份安全技術大會上&#xff0c;持安科技聯合創始人孫維伯應主辦方的特別邀請&#xff0c;發表了主題為“零信任在攻防演練下的最佳實踐”的演講。 孫維伯在2023數字身份安全技術大會上發表演講 以下為本次演講實錄&#xff1a; 我是持安科技的聯合…

【c++】 STL的組件簡介與容器的使用時機

STL六大組件簡介 STL提供了六大組件&#xff0c;彼此之間可以組合套用&#xff0c;這六大組件分別是:容器、算法、迭代器、仿函數、適配器&#xff08;配接器&#xff09;、空間配置器。 容器&#xff1a;各種數據結構&#xff0c;如vector、list、deque、set、map等,用來存放…

微信云開發-- Mac安裝 wx-server-sdk依賴

第一次上傳部署云函數時&#xff0c;會提示安裝依賴wx-server-sdk 一. 判斷是否安裝wx-server-sdk依賴 先創建一個云函數&#xff0c;然后檢查云函數目錄。 如果云函數目錄下只顯示如下圖所示三個文件&#xff0c;說明未安裝依賴。 如果云函數目錄下顯示如下圖所示四個文件&a…