解鎖異步響應力量:淺析Spring WebFlux
1.WebFlux簡介
Spring WebFlux是Spring框架的一個模塊,用于構建反應式、異步和事件驅(qū)動的應用程序。它提供了一種基于Reactive Streams標準的編程模型,能夠處理大量并發(fā)請求和高吞吐量,同時具有較低的資源消耗。
傳統(tǒng)的Servlet API和Spring MVC是基于同步阻塞式編程模型的,而Spring WebFlux則是基于響應式編程模型的,相比較下有如下優(yōu)勢:
并發(fā)處理:
- Servlet API和Spring MVC:采用同步阻塞IO模型,每個請求都會占用一個線程,如果有大量的長時間IO操作或者并發(fā)請求,會導致線程資源耗盡。
- Spring WebFlux:采用非阻塞IO模型,在IO操作完成前不會阻塞線程,因此能夠更高效地處理大量并發(fā)請求。
編程模型:
- Servlet API和Spring MVC:基于Servlet規(guī)范,通常使用注解或者配置文件來定義控制器和URL映射。
- Spring WebFlux:提供了注解式編程和函數(shù)式編程兩種風格的路由定義方式,使得開發(fā)者可以選擇更適合自己項目的方式進行開發(fā)。
線程模型:
- Servlet API和Spring MVC:每個請求需要綁定一個線程,線程池負責管理這些線程。
- Spring WebFlux:基于事件驅(qū)動和響應式流,不需要為每個請求分配獨立線程,能夠更有效地利用系統(tǒng)資源。
應用場景:
- Servlet API和Spring MVC:更適合傳統(tǒng)的同步IO應用,如CRUD操作等。
- Spring WebFlux:適合處理I/O密集型應用,如服務器推送事件、WebSocket通信以及需要處理大量并發(fā)請求的場景。
2.響應式編程
談到Spring WebFlux就需要著重解釋下響應式編程模型,響應式編程是一種面向數(shù)據(jù)流和變化傳播的編程范式。它倡導使用異步數(shù)據(jù)流來構建事件驅(qū)動的、可擴展的應用程序。在響應式編程中,數(shù)據(jù)和事件以流的形式進行處理,并且整個系統(tǒng)會對這些流式數(shù)據(jù)進行監(jiān)聽和響應。主要優(yōu)勢如下:
- 異步和非阻塞:響應式編程采用異步編程模型,能夠更高效地利用系統(tǒng)資源,同時避免線程阻塞,提高系統(tǒng)的并發(fā)處理能力。
- 事件驅(qū)動:通過訂閱數(shù)據(jù)流和事件,系統(tǒng)能夠更靈活地響應外部輸入,適合處理實時性要求高的場景。
- 可擴展性:采用反應式編程模型可以輕松地構建可擴展的系統(tǒng),因為它強調(diào)組件間的解耦和消息傳遞。
- 簡潔清晰:響應式編程模型通常使用函數(shù)式風格,代碼更加簡潔清晰,易于理解和維護。
舉個例子:
假設我們需要實時獲取多個城市的天氣數(shù)據(jù)并展示給用戶,這就涉及到了異步獲取數(shù)據(jù)和實時推送數(shù)據(jù)給前端的需求。
在傳統(tǒng)的同步編程模型中,如果我們采用每個城市一個線程的方式來獲取天氣數(shù)據(jù),可能會因為網(wǎng)絡IO等待而造成大量線程阻塞,浪費系統(tǒng)資源。而且隨著城市數(shù)量的增多,線程管理和資源利用將變得更加困難。
通過采用響應式編程模型,我們可以將天氣數(shù)據(jù)看作一個反應式流,通過訂閱這個數(shù)據(jù)流,可以實時地獲取各個城市的天氣信息。當某個城市的天氣數(shù)據(jù)發(fā)生變化時,系統(tǒng)能夠及時地向訂閱者發(fā)送新的數(shù)據(jù),從而實現(xiàn)實時更新。
另外,由于天氣數(shù)據(jù)的獲取和推送是異步非阻塞的,不需要為每個城市的天氣數(shù)據(jù)分配獨立的線程,能夠更高效地利用系統(tǒng)資源,提高系統(tǒng)的并發(fā)處理能力。
3.Spring WebFlux核心組件
Spring WebFlux 框架由以下核心組件組成:
(1)Handler
Handler是Spring WebFlux中用于處理請求的核心組件。它可以是一個函數(shù)式的處理器,也可以是一個注解式的控制器(類似于Spring MVC中的Controller)。當接收到一個HTTP請求時,WebFlux會根據(jù)路由規(guī)則把請求映射到對應的Handler上,然后由Handler來實際處理請求并生成響應。在函數(shù)式編程風格中,Handler通常是一個處理器函數(shù),而在注解式編程中,Handler通常是一個帶有@RequestMapping注解的控制器方法。
(2)核心控制器DispatcherHandler
核心控制器DispatcherHandler等同于阻塞方式的DispatcherServlet,DispatcherHandler實現(xiàn)ApplicationContextAware,那么必然會調(diào)用setApplicationContext方法。
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
}
initStrategies初始化
獲取HandlerMapping,HandlerAdapter,HandlerResultHandler的所有實例
protected void initStrategies(ApplicationContext context) {
//獲取HandlerMapping及其子類型的bean
//HandlerMapping根據(jù)請求request獲取handler執(zhí)行鏈
Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
//排序
AnnotationAwareOrderComparator.sort(mappings);
this.handlerMappings = Collections.unmodifiableList(mappings);
//獲取HandlerAdapter及其子類型的bean
Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false);
this.handlerAdapters = new ArrayList<>(adapterBeans.values());
//排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
//獲取HandlerResultHandler及其子類型的bean
Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerResultHandler.class, true, false);
this.resultHandlers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(this.resultHandlers);
}
DispatcherHandler的總體流程
- 通過HandlerMapping(和DispatcherServlet中的HandlerMapping不同)獲取到HandlerAdapter放到ServerWebExchange的屬性中
- 獲取到HandlerAdapter后觸發(fā)handle方法,得到HandlerResult
- 通過HandlerResult,觸發(fā)handleResult,針對不同的返回類找到不同的HandlerResultHandler如視圖渲染ViewResolutionResultHandler、ServerResponseResultHandler、ResponseBodyResultHandler、ResponseEntityResultHandler不同容器有不同的實現(xiàn),如Reactor,Jetty,Tomcat等。
(3)Router
Router用于定義URL路由規(guī)則,將請求映射到對應的Handler上。它負責根據(jù)請求的URL路徑和HTTP方法找到對應的Handler,并將請求轉發(fā)給該Handler進行處理。Router可以通過多種方式定義,包括函數(shù)式路由定義和注解式路由定義。在函數(shù)式編程風格中,我們可以使用Java 8的lambda表達式和RouterFunctions來定義路由規(guī)則;而在注解式編程中,我們可以使用類似@RequestMapping和@RestController這樣的注解來定義路由規(guī)則。
(4)RouterFunction
RouterFunction是用于定義路由規(guī)則的主要接口,它可以將HTTP請求映射到對應的處理函數(shù)。通過RouterFunction,我們可以根據(jù)請求的URL路徑和HTTP方法找到對應的Handler,并將請求轉發(fā)給該Handler進行處理。
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> helloRouterFunction(HelloHandler helloHandler) {
return route(GET("/hello"), helloHandler::handleHello);
}
}
在這段示例代碼中,我們首先通過@Configuration注解標記了RouterConfig類,使其成為Spring的配置類。然后,我們使用@Bean注解定義了一個名為helloRouterFunction的Bean,它返回一個RouterFunction<ServerResponse>實例。
這里的route(GET("/hello"), helloHandler::handleHello)表示當收到GET請求且路徑為"/hello"時,將請求轉發(fā)給helloHandler的handleHello方法進行處理。這里使用了靜態(tài)導入,使得可以直接調(diào)用GET方法,簡化路由規(guī)則的定義。
@Component
public class HelloHandler {
public Mono<ServerResponse> handleHello(ServerRequest request) {
return ServerResponse.ok().bodyValue("Hello, WebFlux!");
}
}
在HelloHandler類中,我們通過@Component注解標記為Spring管理的組件,并編寫了handleHello方法來處理請求。該方法返回一個Mono<ServerResponse>,在本例中是將"Hello, WebFlux!"字符串作為響應體返回給客戶端。
(5)WebHandler
WebHandler是處理請求和生成響應的抽象接口,它充當了請求和響應對象之間的橋梁。在WebFlux中,WebHandler接口定義了handle方法,用于處理ServerHttpRequest和ServerHttpResponse對象。它提供了一種統(tǒng)一的處理機制,使得不同類型的Handler可以與請求-響應生命周期進行交互。
4.選WebFlux還是Spring MVC?
首先你需要明確一點就是:WebFlux不是Spring MVC的替代方案,WebFlux并不能使接口的響應時間縮短,它僅僅能夠提升吞吐量和伸縮性 。雖然WebFlux也可以被運行在Servlet容器上(需是Servlet 3.1+以上的容器),但是WebFlux
主要還是應用在異步非阻塞編程模型,而Spring MVC是同步阻塞的,如果你目前在Spring MVC框架中大量使用非同步方案,那么,WebFlux才是你想要的,否則,使用Spring MVC才是你的首選。
在微服務架構中,Spring MVC和WebFlux可以混合使用,比如已經(jīng)提到的,對于那些IO密集型服務(如網(wǎng)關),我們就可以使用WebFlux來實現(xiàn)。
從上圖中,可以一眼看出Spring MVC和Spring WebFlux的相同點和不同點:
相同點:
- 都可以使用Spring MVC注解,如@Controller,方便我們在兩個Web框架中自由轉換;
- 均可以使用Tomcat,Jetty,Undertow Servlet容器(Servlet 3.1+);
- ...
不同點:
- Spring MVC因為是使用的同步阻塞式,更方便開發(fā)人員編寫功能代碼,Debug測試等,一般來說,如果Spring MVC能夠滿足的場景,就盡量不要用WebFlux;
- WebFlux默認情況下使用Netty作為服務器;
- WebFlux不支持MySql;
5.WebFlux與Spring框架集成
pom.xml:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>webflux-demo</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
</project>
在上述配置中,我們使用了Spring Boot的起步依賴
spring-boot-starter-webflux來引入WebFlux相關的依賴。接下來,我們將創(chuàng)建一個簡單的Controller并將其拆分到單獨的類中。
首先,創(chuàng)建一個名為 HelloController 的類:
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
public class HelloController {
public RouterFunction<ServerResponse> route() {
return route(GET("/hello"), this::handleHello);
}
private Mono<ServerResponse> handleHello(ServerRequest request) {
return ServerResponse.ok().bodyValue("Hello, WebFlux!");
}
}
然后在主應用程序中注入HelloController:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
@SpringBootApplication
public class WebFluxIntegrationDemo {
public static void main(String[] args) {
SpringApplication.run(WebFluxIntegrationDemo.class, args);
}
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(HelloController helloController) {
return helloController.route();
}
}
在這個示例中,我們通過@Bean注解將HelloController注入到主應用程序中,并且調(diào)用其route方法來定義路由規(guī)則。
6.總結
關于pring WebFlux 很多很多,本文主要總結如下幾點
優(yōu)點:
- 非阻塞和異步:能夠處理大量并發(fā)請求,提高系統(tǒng)的吞吐量。
- 響應式編程模型:支持響應式數(shù)據(jù)流處理,適合處理實時、事件驅(qū)動的應用場景。
- 彈性和擴展性:能夠輕松地與反應式數(shù)據(jù)流、異步數(shù)據(jù)庫等集成,方便構建彈性和可擴展的系統(tǒng)。
缺點:
- 學習曲線較陡峭:相對于傳統(tǒng)的Servlet編程模型,對于開發(fā)人員需要一定的學習成本。
- 對某些傳統(tǒng)的基于Servlet的庫和框架的兼容性可能不夠理想。
適用范圍:
- 需要處理高并發(fā)、實時性要求高的網(wǎng)絡應用程序;
- 適合微服務架構和需要異步通信的項目;
- 處理大規(guī)模數(shù)據(jù)流和事件驅(qū)動的應用場景。