亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2023年08月30日 15:42:01   作者:JavaEdge.  
這篇文章主要介紹了Spring WebFlux使用函數(shù)式編程模型構建異步非阻塞服務,重點介紹如何使用函數(shù)式編程模型創(chuàng)建響應式 RESTful 服務,這種編程模型與傳統(tǒng)的基于 Spring MVC 構建 RESTful 服務的方法有較大差別,感興趣的朋友跟隨小編一起看看吧

1 前言

上文引入了 Spring 框架中專門用于構建響應式 Web 服務的 WebFlux 框架,同時我也給出了兩種創(chuàng)建 RESTful 風格 HTTP 端點實現(xiàn)方法中的一種,即注解編程模型。

本文介紹另一種實現(xiàn)方法——如何使用函數(shù)式編程模型創(chuàng)建響應式 RESTful 服務,這種編程模型與傳統(tǒng)的基于 Spring MVC 構建 RESTful 服務的方法有較大差別。

2 WebFlux 函數(shù)式編程模型

回顧Spring WebFlux系統(tǒng)架構圖:

圖后半部分,Spring WebFlux 中,函數(shù)式編程模型的核心概念Router Functions,對標 Spring MVC 的 @Controller、@RequestMapping 等標準注解。而 Router Functions 則提供一套函數(shù)式風格API,最重要的就是 Router、Handler 接口??珊唵螌ⅲ?/p>

  • Router 對應成 RequestMapping
  • Controller 對應為 Handler

當我發(fā)起一個遠程調用,傳入的 HTTP 請求由 HandlerFunction 處理, HandlerFunction 本質上是一個接收 ServerRequest 并返回 Mono 的函數(shù)。ServerRequest 和 ServerResponse 是一對不可變接口,用來提供對底層 HTTP 消息的友好訪問。

3 ServerRequest

代表請求對象,可訪問各種 HTTP 請求元素,包括請求方法、URI 和參數(shù),以及通過單獨的 ServerRequest.Headers 獲取 HTTP 請求頭信息。

ServerRequest 通過一系列 bodyToMono() 和 bodyToFlux() 方法提供對請求消息體進行訪問的途徑。例如,如果我們希望將請求消息體提取為 Mono 類型的對象,可以使用如下方法。

Mono<String> string = request.bodyToMono(String.class);

而如果我們希望將請求消息體提取為 Flux 類型的對象,可以使用如下方法,其中 Order 是可以從請求消息體反序列化的實體類。

Flux<Order> order = request.bodyToFlux(Order.class);

上述的 bodyToMono() 和 bodyToFlux() 兩個方法實際上是通用的 ServerRequest.body(BodyExtractor) 工具方法的快捷方式,該方法如下所示。

<T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor);

BodyExtractor 是一種請求消息體的提取器,允許我們編寫自己的提取邏輯。請注意 BodyExtractor 提取的對象是一個 ServerHttpRequest 類型的實例,而這個 ServerHttpRequest 是非阻塞的,與之對應的還有一個 ServerHttpResponse 對象。

響應式 Web 操作的正是這組非阻塞的:

  • ServerHttpRequest
  • ServerHttpResponse

而不再是 Spring MVC 里的傳統(tǒng):

  • HttpServletRequest
  • HttpServletResponse

若不需要實現(xiàn)定制化的提取邏輯,可使用框架提供的常用的 BodyExtractors 實例。通過 BodyExtractors,上例可替換為。

Mono<String> string = 
	request.body(BodyExtractors.toMono(String.class);
Flux<Person> Order= 
	request.body(BodyExtractors.toFlux(Order.class);

ServerResponse

與ServerRequest 對應,ServerResponse 提供對 HTTP 響應的訪問。由于不可變,因此可用構建器創(chuàng)建一個新 ServerResponse。

構建器允許設置響應狀態(tài)、添加響應標題并提供響應的具體內容。如下示例演示如何通過 ok() 方法創(chuàng)建代表 200 狀態(tài)碼的響應,其中我將響應體的類型設置為 JSON 格式,響應具體內容是 Mono 對象。

Mono<Order> order = …;
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
     .body(order);

通過 body() 方法來加載響應內容是構建 ServerResponse 最常見的方法,這里我們將 Order 對象作為返回值。如果想要返回各種類型的對象,我們也可以使用 BodyInserters 工具類所提供的構建方法,如常見的 fromObject() 和 fromPublisher() 方法等。以下示例代碼中,我們通過 fromObject() 方法直接返回一個 “Hello World”。

ServerResponse.ok().body(BodyInserters.fromObject("Hello World"));

上述方法的背后實際上是利用 BodyBuilder 接口中的一組 body() 方法,來構建一個 ServerResponse 對象,典型的 body() 方法如下所示。

Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);

這里我們同樣看到了非阻塞式的 ServerHttpResponse 對象。這種 body() 方法比較常見的用法是返回新增和更新操作的結果,你在本講后續(xù)的內容中將會看到這種使用方法。

HandlerFunction

將 ServerRequest 和 ServerResponse 組合在一起就可創(chuàng)建 HandlerFunction。

public interface HandlerFunction<T extends ServerResponse> {
    Mono<T> handle(ServerRequest request);
}

可通過實現(xiàn) HandlerFunction#handle() 創(chuàng)建定制化的請求響應處理機制。

簡單的“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() 方法返回一個 String 類型的消息體。

通常,針對某個領域實體都存在 CRUD 等常見的操作,所以需要編寫多個類似的處理函數(shù),煩瑣。推薦將多個處理函數(shù)分組到一個專門 Handler 類。

RouterFunction

已可通過 HandlerFunction 創(chuàng)建請求的處理邏輯,接下來需要把具體請求與這種處理邏輯關聯(lián)起來,RouterFunction 可以幫助我們實現(xiàn)這一目標。RouterFunction 與傳統(tǒng) SpringMVC 中的 @RequestMapping 注解功能類似。

創(chuàng)建 RouterFunction 最常見做法是使用 route 方法,該方法通過使用請求謂詞和處理函數(shù)創(chuàng)建一個 ServerResponse 對象。

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。如果請求與特定路由匹配,則返回處理函數(shù)的結果,否則就返回空Mono。

RequestPredicates 工具類提供了常用的謂詞,能夠實現(xiàn)包括基于路徑、HTTP 方法、內容類型等條件的自動匹配。

簡單 RouterFunction 示例,實現(xiàn)對 "/hello-world"請求路徑的自動路由:

RouterFunction<ServerResponse> helloWorldRoute =                RouterFunctions.route(RequestPredicates.path("/hello-world"),
            new HelloWorldHandlerFunction());

類似的,我們應該把 RouterFunction 和各種 HandlerFunction 按照需求結合起來一起使用,常見的做法也是根據(jù)領域對象來設計對應的 RouterFunction。

路由機制的優(yōu)勢在于它的組合型。兩個路由功能可以組合成一個新的路由功能,并通過一定的評估方法路由到其中任何一個處理函數(shù)。如果第一個路由的謂詞不匹配,則第二個謂詞會被評估。請注意組合的路由器功能會按照順序進行評估,因此在通用功能之前放置一些特定功能是一項最佳實踐。在 RouterFunction 中,同樣提供了對應的組合方法來實現(xiàn)這一目標,請看下面的代碼。

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));
}

我們可以通過調用上述兩個方法中的任意一個來組合兩個路由功能,其中后者相當于 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) 方法的實現(xiàn)如下所示。

public static RequestPredicate GET(String pattern) {
        return method(HttpMethod.GET).and(path(pattern));
}

可以看到,該方法是 RequestPredicates.method(HttpMethod.GET) 和 RequestPredicates.path(String) 的組合。我們可以通過調用 RequestPredicate.and(RequestPredicate) 方法或 RequestPredicate.or(RequestPredicate) 方法來構建復雜的請求謂詞。

案例集成:ReactiveSpringCSS 中的 Web 服務

customer-service 分別需要訪問 account-service 和 order-service 服務中的 Web 服務。

已基于注解編程模型實現(xiàn)了 account-service 中的 AccountController。今天演示 order-service 中 Web 服務實現(xiàn)過程。

基于函數(shù)式編程模型,在 order-service 中,編寫 OrderHandler 專門實現(xiàn)根據(jù) OrderNumber 獲取 Order 領域實體的處理函數(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)建了一個 OrderHandler 類,然后注入 OrderService 并實現(xiàn)了一個 getOrderByOrderNumber() 處理函數(shù)。

現(xiàn)在我們已經(jīng)具備了 OrderHandler,就可以創(chuà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);
    }
}

在這個示例中,我們通過訪問“/orders/{orderNumber}”端點就會自動觸發(fā) orderHandler 中的 getOrderByOrderNumber() 方法并返回相應的 ServerResponse。

接下來,假設我們已經(jīng)分別通過遠程調用獲取了目標 Account 對象和 Order 對象,那么 generateCustomerTicket 方法的執(zhí)行流程就可以明確了?;陧憫骄幊痰膶崿F(xiàn)方法,我們可以得到如下所示的示例代碼。

public Mono<CustomerTicket> generateCustomerTicket(String accountId, String orderNumber) {
        // 創(chuàng)建 CustomerTicket 對象
        CustomerTicket customerTicket = new CustomerTicket();
        customerTicket.setId("C_" + UUID.randomUUID().toString());
        // 從遠程 account-service 獲取 Account 對象
        Mono<AccountMapper> accountMapper = getRemoteAccountByAccountId(accountId);
        // 從遠程 order-service 中獲取 Order 對象
        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);
            }
            // 設置 CustomerTicket 對象屬性
            customerTicket.setAccountId(account.getId());
            customerTicket.setOrderNumber(order.getOrderNumber());
            customerTicket.setCreateTime(new Date());
            customerTicket.setDescription("TestCustomerTicket");
            return Mono.just(customerTicket);
        });
        // 保存 CustomerTicket 對象并返回
        return monoCustomerTicket.flatMap(customerTicketRepository::save);
}

顯然,這里的 getRemoteAccountById 和 getRemoteOrderByOrderNumber 方法都涉及了非阻塞式的遠程 Web 服務的調用,這一過程我們將放在下一講中詳細介紹。

請注意,到這里時使用了 Reactor 框架中的 zip 操作符,將 accountMapper 流中的元素與 orderMapper 流中的元素按照一對一的方式進行合并,合并之后得到一個 Tuple2 對象。然后,我們再分別從這個 Tuple2 對象中獲取 AccountMapper 和 OrderMapper 對象,并將它們的屬性填充到所生成的 CustomerTicket 對象中。最后,我們通過 flatMap 操作符調用了 customerTicketRepository 的 save 方法完成了數(shù)據(jù)的持久化。這是 zip 和 flatMap 這兩個操作符非常經(jīng)典的一種應用場景,你需要熟練掌握。

總結

好了,那么本講內容就說到這。延續(xù)上一講,我們接著討論了 Spring WebFlux 的使用方法,并給出了基于函數(shù)式編程模型的 RESTful 端點創(chuàng)建方法。在這種開發(fā)模型中,重點把握:

  • ServerRequest
  • ServerResponse
  • HandlerFunction
  • RouterFunction

核心對象的使用方法。

FAQ

WebFlux 函數(shù)式編程模型中包含哪些核心編程對象嗎?

現(xiàn)在,我們已經(jīng)通過 WebFlux 構建了響應式 Web 服務,下一步就是如何來消費它們了。Spring 也專門提供了一個非阻塞式的 WebClient 工具類來完成這一目標,下一講我就來和你系統(tǒng)地討論這個工具類的使用方法,到時見。

到此這篇關于Spring WebFlux使用函數(shù)式編程模型構建異步非阻塞服務的文章就介紹到這了,更多相關Spring WebFlux構建異步非阻塞服務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java 注冊時發(fā)送激活郵件和激活的實現(xiàn)示例

    Java 注冊時發(fā)送激活郵件和激活的實現(xiàn)示例

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

    SpringBoot環(huán)境Druid數(shù)據(jù)源使用及特點

    Druid 是目前比較流行的高性能的,分布式列存儲的OLAP框架(具體來說是MOLAP)。本文給大家分享SpringBoot環(huán)境Druid數(shù)據(jù)源使用及特點介紹,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • 圖解Spring Security 中用戶是如何實現(xiàn)登錄的

    圖解Spring Security 中用戶是如何實現(xiàn)登錄的

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

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

    這篇文章主要介紹了Spring整合Mybatis實操分享,文章首先通過介紹Mybatis的工作原理展開Spring整合Mybatis的詳細內容,需要的小伙伴可以參考一下
    2022-05-05
  • RocketMQ源碼解析broker?啟動流程

    RocketMQ源碼解析broker?啟動流程

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

    SpringBoot 整合jdbc和mybatis的方法

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

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

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

    java反射總結實例詳解

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

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

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

    如何解決java.lang.NoClassDefFoundError:Could not initi

    文章講述了在Java服務器中處理圖形元素時遇到的常見問題,即需要運行X-server,通過在Tomcat/bin/catalina.sh中增加JAVA_OPTS環(huán)境變量并設置-Djava.awt.headless=true,可以解決這個問題,使服務器能夠在沒有圖形界面的情況下運行
    2024-11-11

最新評論