欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring WebFlux使用函數(shù)式編程模型構(gòu)建異步非阻塞服務(wù)

 更新時(shí)間:2023年08月30日 15:42:01   作者:JavaEdge.  
這篇文章主要介紹了Spring WebFlux使用函數(shù)式編程模型構(gòu)建異步非阻塞服務(wù),重點(diǎn)介紹如何使用函數(shù)式編程模型創(chuàng)建響應(yīng)式 RESTful 服務(wù),這種編程模型與傳統(tǒng)的基于 Spring MVC 構(gòu)建 RESTful 服務(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)示例

    這篇文章主要介紹了Java 注冊(cè)時(shí)發(fā)送激活郵件和激活的實(shí)現(xiàn)示例的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • SpringBoot環(huán)境Druid數(shù)據(jù)源使用及特點(diǎn)

    SpringBoot環(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)登錄的

    這篇文章主要介紹了圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的,文中通過(guò)示例代碼和圖片介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Spring整合Mybatis具體代碼實(shí)現(xiàn)流程

    Spring整合Mybatis具體代碼實(shí)現(xiàn)流程

    這篇文章主要介紹了Spring整合Mybatis實(shí)操分享,文章首先通過(guò)介紹Mybatis的工作原理展開(kāi)Spring整合Mybatis的詳細(xì)內(nèi)容,需要的小伙伴可以參考一下
    2022-05-05
  • RocketMQ源碼解析broker?啟動(dòng)流程

    RocketMQ源碼解析broker?啟動(dòng)流程

    這篇文章主要為大家介紹了RocketMQ源碼解析broker啟動(dòng)流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • SpringBoot 整合jdbc和mybatis的方法

    SpringBoot 整合jdbc和mybatis的方法

    該文章主要為記錄如何在SpringBoot項(xiàng)目中整合JDBC和MyBatis,在整合中我會(huì)使用簡(jiǎn)單的用法和測(cè)試用例,感興趣的朋友跟隨小編一起看看吧
    2019-11-11
  • mybatis對(duì)傳入基本類型參數(shù)的判斷方式

    mybatis對(duì)傳入基本類型參數(shù)的判斷方式

    這篇文章主要介紹了mybatis對(duì)傳入基本類型參數(shù)的判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java反射總結(jié)實(shí)例詳解

    java反射總結(jié)實(shí)例詳解

    這篇文章主要結(jié)合實(shí)例形式分析了介紹了java基于反射得到對(duì)象屬性值的方法,Class類,基本數(shù)據(jù)類型,類的反射等,需要的朋友可以參考下
    2017-04-04
  • JDK17安裝教程以及其環(huán)境變量配置教程

    JDK17安裝教程以及其環(huán)境變量配置教程

    環(huán)境變量對(duì)Java初學(xué)者來(lái)說(shuō)真的是一件頭疼的事,本人也經(jīng)歷過(guò)這樣的事情,這篇文章主要給大家介紹了關(guān)于JDK17安裝教程以及其環(huán)境變量配置的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • 如何解決java.lang.NoClassDefFoundError:Could not initialize class java.awt.Color問(wèn)題

    如何解決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

最新評(píng)論