Spring WebFlux使用函數(shù)式編程模型構(gòu)建異步非阻塞服務(wù)
1 前言
上文引入了 Spring 框架中專門(mén)用于構(gòu)建響應(yīng)式 Web 服務(wù)的 WebFlux 框架,同時(shí)我也給出了兩種創(chuàng)建 RESTful 風(fēng)格 HTTP 端點(diǎn)實(shí)現(xiàn)方法中的一種,即注解編程模型。
本文介紹另一種實(shí)現(xiàn)方法——如何使用函數(shù)式編程模型創(chuàng)建響應(yīng)式 RESTful 服務(wù),這種編程模型與傳統(tǒng)的基于 Spring MVC 構(gòu)建 RESTful 服務(wù)的方法有較大差別。
2 WebFlux 函數(shù)式編程模型
回顧Spring WebFlux系統(tǒng)架構(gòu)圖:
圖后半部分,Spring WebFlux 中,函數(shù)式編程模型的核心概念Router Functions,對(duì)標(biāo) Spring MVC 的 @Controller、@RequestMapping 等標(biāo)準(zhǔn)注解。而 Router Functions 則提供一套函數(shù)式風(fēng)格API,最重要的就是 Router、Handler 接口??珊?jiǎn)單將:
- Router 對(duì)應(yīng)成 RequestMapping
- Controller 對(duì)應(yīng)為 Handler
當(dāng)我發(fā)起一個(gè)遠(yuǎn)程調(diào)用,傳入的 HTTP 請(qǐng)求由 HandlerFunction 處理, HandlerFunction 本質(zhì)上是一個(gè)接收 ServerRequest 并返回 Mono 的函數(shù)。ServerRequest 和 ServerResponse 是一對(duì)不可變接口,用來(lái)提供對(duì)底層 HTTP 消息的友好訪問(wèn)。
3 ServerRequest
代表請(qǐng)求對(duì)象,可訪問(wèn)各種 HTTP 請(qǐng)求元素,包括請(qǐng)求方法、URI 和參數(shù),以及通過(guò)單獨(dú)的 ServerRequest.Headers 獲取 HTTP 請(qǐng)求頭信息。
ServerRequest 通過(guò)一系列 bodyToMono() 和 bodyToFlux() 方法提供對(duì)請(qǐng)求消息體進(jìn)行訪問(wèn)的途徑。例如,如果我們希望將請(qǐng)求消息體提取為 Mono 類型的對(duì)象,可以使用如下方法。
Mono<String> string = request.bodyToMono(String.class);
而如果我們希望將請(qǐng)求消息體提取為 Flux 類型的對(duì)象,可以使用如下方法,其中 Order 是可以從請(qǐng)求消息體反序列化的實(shí)體類。
Flux<Order> order = request.bodyToFlux(Order.class);
上述的 bodyToMono() 和 bodyToFlux() 兩個(gè)方法實(shí)際上是通用的 ServerRequest.body(BodyExtractor) 工具方法的快捷方式,該方法如下所示。
<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);
BodyExtractor 是一種請(qǐng)求消息體的提取器,允許我們編寫(xiě)自己的提取邏輯。請(qǐng)注意 BodyExtractor 提取的對(duì)象是一個(gè) ServerHttpRequest 類型的實(shí)例,而這個(gè) ServerHttpRequest 是非阻塞的,與之對(duì)應(yīng)的還有一個(gè) ServerHttpResponse 對(duì)象。
響應(yīng)式 Web 操作的正是這組非阻塞的:
- ServerHttpRequest
- ServerHttpResponse
而不再是 Spring MVC 里的傳統(tǒng):
- HttpServletRequest
- HttpServletResponse
若不需要實(shí)現(xiàn)定制化的提取邏輯,可使用框架提供的常用的 BodyExtractors 實(shí)例。通過(guò) BodyExtractors,上例可替換為。
Mono<String> string = request.body(BodyExtractors.toMono(String.class); Flux<Person> Order= request.body(BodyExtractors.toFlux(Order.class);
ServerResponse
與ServerRequest 對(duì)應(yīng),ServerResponse 提供對(duì) HTTP 響應(yīng)的訪問(wèn)。由于不可變,因此可用構(gòu)建器創(chuàng)建一個(gè)新 ServerResponse。
構(gòu)建器允許設(shè)置響應(yīng)狀態(tài)、添加響應(yīng)標(biāo)題并提供響應(yīng)的具體內(nèi)容。如下示例演示如何通過(guò) ok() 方法創(chuàng)建代表 200 狀態(tài)碼的響應(yīng),其中我將響應(yīng)體的類型設(shè)置為 JSON 格式,響應(yīng)具體內(nèi)容是 Mono 對(duì)象。
Mono<Order> order = …; ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(order);
通過(guò) body() 方法來(lái)加載響應(yīng)內(nèi)容是構(gòu)建 ServerResponse 最常見(jiàn)的方法,這里我們將 Order 對(duì)象作為返回值。如果想要返回各種類型的對(duì)象,我們也可以使用 BodyInserters 工具類所提供的構(gòu)建方法,如常見(jiàn)的 fromObject() 和 fromPublisher() 方法等。以下示例代碼中,我們通過(guò) fromObject() 方法直接返回一個(gè) “Hello World”。
ServerResponse.ok().body(BodyInserters.fromObject("Hello World"));
上述方法的背后實(shí)際上是利用 BodyBuilder 接口中的一組 body() 方法,來(lái)構(gòu)建一個(gè) ServerResponse 對(duì)象,典型的 body() 方法如下所示。
Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);
這里我們同樣看到了非阻塞式的 ServerHttpResponse 對(duì)象。這種 body() 方法比較常見(jiàn)的用法是返回新增和更新操作的結(jié)果,你在本講后續(xù)的內(nèi)容中將會(huì)看到這種使用方法。
HandlerFunction
將 ServerRequest 和 ServerResponse 組合在一起就可創(chuàng)建 HandlerFunction。
public interface HandlerFunction<T extends ServerResponse> { Mono<T> handle(ServerRequest request); }
可通過(guò)實(shí)現(xiàn) HandlerFunction#handle() 創(chuàng)建定制化的請(qǐng)求響應(yīng)處理機(jī)制。
簡(jiǎn)單的“Hello World”處理函數(shù)。
public class HelloWorldHandlerFunction implements HandlerFunction<ServerResponse> { @Override public Mono<ServerResponse> handle(ServerRequest request) { return ServerResponse.ok().body( BodyInserters.fromObject("Hello World")); } };
這里使用了前面介紹的 ServerResponse 所提供的 body() 方法返回一個(gè) String 類型的消息體。
通常,針對(duì)某個(gè)領(lǐng)域?qū)嶓w都存在 CRUD 等常見(jiàn)的操作,所以需要編寫(xiě)多個(gè)類似的處理函數(shù),煩瑣。推薦將多個(gè)處理函數(shù)分組到一個(gè)專門(mén) Handler 類。
RouterFunction
已可通過(guò) HandlerFunction 創(chuàng)建請(qǐng)求的處理邏輯,接下來(lái)需要把具體請(qǐng)求與這種處理邏輯關(guān)聯(lián)起來(lái),RouterFunction 可以幫助我們實(shí)現(xiàn)這一目標(biāo)。RouterFunction 與傳統(tǒng) SpringMVC 中的 @RequestMapping 注解功能類似。
創(chuàng)建 RouterFunction 最常見(jiàn)做法是使用 route 方法,該方法通過(guò)使用請(qǐng)求謂詞和處理函數(shù)創(chuàng)建一個(gè) ServerResponse 對(duì)象。
public static <T extends ServerResponse> RouterFunction<T> route( RequestPredicate predicate, HandlerFunction<T> handlerFunction) { return new DefaultRouterFunction<>(predicate, handlerFunction); }
RouterFunction 的核心邏輯位于這里的 DefaultRouterFunction 類中,該類的 route() 方法如下所示。
public Mono<HandlerFunction<T>> route(ServerRequest request) { if (this.predicate.test(request)) { if (logger.isTraceEnabled()) { String logPrefix = request.exchange().getLogPrefix(); logger.trace(logPrefix + String.format("Matched %s", this.predicate)); } return Mono.just(this.handlerFunction); } else { return Mono.empty(); } }
該方法將傳入的 ServerRequest 路由到具體的處理函數(shù) HandlerFunction。如果請(qǐng)求與特定路由匹配,則返回處理函數(shù)的結(jié)果,否則就返回空Mono。
RequestPredicates 工具類提供了常用的謂詞,能夠?qū)崿F(xiàn)包括基于路徑、HTTP 方法、內(nèi)容類型等條件的自動(dòng)匹配。
簡(jiǎn)單 RouterFunction 示例,實(shí)現(xiàn)對(duì) "/hello-world"請(qǐng)求路徑的自動(dòng)路由:
RouterFunction<ServerResponse> helloWorldRoute = RouterFunctions.route(RequestPredicates.path("/hello-world"), new HelloWorldHandlerFunction());
類似的,我們應(yīng)該把 RouterFunction 和各種 HandlerFunction 按照需求結(jié)合起來(lái)一起使用,常見(jiàn)的做法也是根據(jù)領(lǐng)域?qū)ο髞?lái)設(shè)計(jì)對(duì)應(yīng)的 RouterFunction。
路由機(jī)制的優(yōu)勢(shì)在于它的組合型。兩個(gè)路由功能可以組合成一個(gè)新的路由功能,并通過(guò)一定的評(píng)估方法路由到其中任何一個(gè)處理函數(shù)。如果第一個(gè)路由的謂詞不匹配,則第二個(gè)謂詞會(huì)被評(píng)估。請(qǐng)注意組合的路由器功能會(huì)按照順序進(jìn)行評(píng)估,因此在通用功能之前放置一些特定功能是一項(xiàng)最佳實(shí)踐。在 RouterFunction 中,同樣提供了對(duì)應(yīng)的組合方法來(lái)實(shí)現(xiàn)這一目標(biāo),請(qǐng)看下面的代碼。
default RouterFunction<T> and(RouterFunction<T> other) { return new RouterFunctions.SameComposedRouterFunction<>(this, other); } default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) { return and(RouterFunctions.route(predicate, handlerFunction)); }
我們可以通過(guò)調(diào)用上述兩個(gè)方法中的任意一個(gè)來(lái)組合兩個(gè)路由功能,其中后者相當(dāng)于 RouterFunction.and() 方法與 RouterFunctions.route() 方法的集成。以下代碼演示了 RouterFunctions 的組合特性。
RouterFunction<ServerResponse> personRoute = route(GET("/orders/{id}").and(accept(APPLICATION_JSON)), personHandler::getOrderById) .andRoute(GET("/orders").and(accept(APPLICATION_JSON)), personHandler::getOrders) .andRoute(POST("/orders").and(contentType(APPLICATION_JSON)), personHandler::createOrder);
RequestPredicates 工具類所提供的大多數(shù)謂詞也具備組合特性。例如, RequestPredicates.GET(String) 方法的實(shí)現(xiàn)如下所示。
public static RequestPredicate GET(String pattern) { return method(HttpMethod.GET).and(path(pattern)); }
可以看到,該方法是 RequestPredicates.method(HttpMethod.GET) 和 RequestPredicates.path(String) 的組合。我們可以通過(guò)調(diào)用 RequestPredicate.and(RequestPredicate) 方法或 RequestPredicate.or(RequestPredicate) 方法來(lái)構(gòu)建復(fù)雜的請(qǐng)求謂詞。
案例集成:ReactiveSpringCSS 中的 Web 服務(wù)
customer-service 分別需要訪問(wèn) account-service 和 order-service 服務(wù)中的 Web 服務(wù)。
已基于注解編程模型實(shí)現(xiàn)了 account-service 中的 AccountController。今天演示 order-service 中 Web 服務(wù)實(shí)現(xiàn)過(guò)程。
基于函數(shù)式編程模型,在 order-service 中,編寫(xiě) OrderHandler 專門(mén)實(shí)現(xiàn)根據(jù) OrderNumber 獲取 Order 領(lǐng)域?qū)嶓w的處理函數(shù)
@Configuration public class OrderHandler { @Autowired private OrderService orderService; public Mono<ServerResponse> getOrderByOrderNumber(ServerRequest request) { String orderNumber = request.pathVariable("orderNumber"); return ServerResponse.ok().body(this.orderService.getOrderByOrderNumber(orderNumber), Order.class); } }
上述代碼示例中,我們創(chuàng)建了一個(gè) OrderHandler 類,然后注入 OrderService 并實(shí)現(xiàn)了一個(gè) getOrderByOrderNumber() 處理函數(shù)。
現(xiàn)在我們已經(jīng)具備了 OrderHandler,就可以創(chuàng)建對(duì)應(yīng)的 OrderRouter 了,示例代碼如下。
@Configuration public class OrderRouter { @Bean public RouterFunction<ServerResponse> routeOrder(OrderHandler orderHandler) { return RouterFunctions.route( RequestPredicates.GET("/orders/{orderNumber}") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), orderHandler::getOrderByOrderNumber); } }
在這個(gè)示例中,我們通過(guò)訪問(wèn)“/orders/{orderNumber}”端點(diǎn)就會(huì)自動(dòng)觸發(fā) orderHandler 中的 getOrderByOrderNumber() 方法并返回相應(yīng)的 ServerResponse。
接下來(lái),假設(shè)我們已經(jīng)分別通過(guò)遠(yuǎn)程調(diào)用獲取了目標(biāo) Account 對(duì)象和 Order 對(duì)象,那么 generateCustomerTicket 方法的執(zhí)行流程就可以明確了?;陧憫?yīng)式編程的實(shí)現(xiàn)方法,我們可以得到如下所示的示例代碼。
public Mono<CustomerTicket> generateCustomerTicket(String accountId, String orderNumber) { // 創(chuàng)建 CustomerTicket 對(duì)象 CustomerTicket customerTicket = new CustomerTicket(); customerTicket.setId("C_" + UUID.randomUUID().toString()); // 從遠(yuǎn)程 account-service 獲取 Account 對(duì)象 Mono<AccountMapper> accountMapper = getRemoteAccountByAccountId(accountId); // 從遠(yuǎn)程 order-service 中獲取 Order 對(duì)象 Mono<OrderMapper> orderMapper = getRemoteOrderByOrderNumber(orderNumber); Mono<CustomerTicket> monoCustomerTicket = Mono.zip(accountMapper, orderMapper).flatMap(tuple -> { AccountMapper account = tuple.getT1(); OrderMapper order = tuple.getT2(); if(account == null || order == null) { return Mono.just(customerTicket); } // 設(shè)置 CustomerTicket 對(duì)象屬性 customerTicket.setAccountId(account.getId()); customerTicket.setOrderNumber(order.getOrderNumber()); customerTicket.setCreateTime(new Date()); customerTicket.setDescription("TestCustomerTicket"); return Mono.just(customerTicket); }); // 保存 CustomerTicket 對(duì)象并返回 return monoCustomerTicket.flatMap(customerTicketRepository::save); }
顯然,這里的 getRemoteAccountById 和 getRemoteOrderByOrderNumber 方法都涉及了非阻塞式的遠(yuǎn)程 Web 服務(wù)的調(diào)用,這一過(guò)程我們將放在下一講中詳細(xì)介紹。
請(qǐng)注意,到這里時(shí)使用了 Reactor 框架中的 zip 操作符,將 accountMapper 流中的元素與 orderMapper 流中的元素按照一對(duì)一的方式進(jìn)行合并,合并之后得到一個(gè) Tuple2 對(duì)象。然后,我們?cè)俜謩e從這個(gè) Tuple2 對(duì)象中獲取 AccountMapper 和 OrderMapper 對(duì)象,并將它們的屬性填充到所生成的 CustomerTicket 對(duì)象中。最后,我們通過(guò) flatMap 操作符調(diào)用了 customerTicketRepository 的 save 方法完成了數(shù)據(jù)的持久化。這是 zip 和 flatMap 這兩個(gè)操作符非常經(jīng)典的一種應(yīng)用場(chǎng)景,你需要熟練掌握。
總結(jié)
好了,那么本講內(nèi)容就說(shuō)到這。延續(xù)上一講,我們接著討論了 Spring WebFlux 的使用方法,并給出了基于函數(shù)式編程模型的 RESTful 端點(diǎn)創(chuàng)建方法。在這種開(kāi)發(fā)模型中,重點(diǎn)把握:
- ServerRequest
- ServerResponse
- HandlerFunction
- RouterFunction
核心對(duì)象的使用方法。
FAQ
WebFlux 函數(shù)式編程模型中包含哪些核心編程對(duì)象嗎?
現(xiàn)在,我們已經(jīng)通過(guò) WebFlux 構(gòu)建了響應(yīng)式 Web 服務(wù),下一步就是如何來(lái)消費(fèi)它們了。Spring 也專門(mén)提供了一個(gè)非阻塞式的 WebClient 工具類來(lái)完成這一目標(biāo),下一講我就來(lái)和你系統(tǒng)地討論這個(gè)工具類的使用方法,到時(shí)見(jiàn)。
到此這篇關(guān)于Spring WebFlux使用函數(shù)式編程模型構(gòu)建異步非阻塞服務(wù)的文章就介紹到這了,更多相關(guān)Spring WebFlux構(gòu)建異步非阻塞服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 注冊(cè)時(shí)發(fā)送激活郵件和激活的實(shí)現(xiàn)示例
這篇文章主要介紹了Java 注冊(cè)時(shí)發(fā)送激活郵件和激活的實(shí)現(xiàn)示例的相關(guān)資料,需要的朋友可以參考下2017-07-07SpringBoot環(huán)境Druid數(shù)據(jù)源使用及特點(diǎn)
Druid 是目前比較流行的高性能的,分布式列存儲(chǔ)的OLAP框架(具體來(lái)說(shuō)是MOLAP)。本文給大家分享SpringBoot環(huán)境Druid數(shù)據(jù)源使用及特點(diǎn)介紹,感興趣的朋友跟隨小編一起看看吧2021-07-07圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的
這篇文章主要介紹了圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的,文中通過(guò)示例代碼和圖片介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Spring整合Mybatis具體代碼實(shí)現(xiàn)流程
這篇文章主要介紹了Spring整合Mybatis實(shí)操分享,文章首先通過(guò)介紹Mybatis的工作原理展開(kāi)Spring整合Mybatis的詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-05-05mybatis對(duì)傳入基本類型參數(shù)的判斷方式
這篇文章主要介紹了mybatis對(duì)傳入基本類型參數(shù)的判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03如何解決java.lang.NoClassDefFoundError:Could not initi
文章講述了在Java服務(wù)器中處理圖形元素時(shí)遇到的常見(jiàn)問(wèn)題,即需要運(yùn)行X-server,通過(guò)在Tomcat/bin/catalina.sh中增加JAVA_OPTS環(huán)境變量并設(shè)置-Djava.awt.headless=true,可以解決這個(gè)問(wèn)題,使服務(wù)器能夠在沒(méi)有圖形界面的情況下運(yùn)行2024-11-11