响应式编程WebFlux基础实战练习

发布时间:2024年01月12日

什么是WebFlux?

响应式编程WebFlux是Spring Framework 5中引入的一个全新的响应式编程框架,它基于Reactor库构建,提供了异步和非阻塞的事件处理。WebFlux框架设计用于处理长时间运行的异步任务,例如网络调用或数据库操作,而不会阻塞线程。这样可以提高系统的吞吐量和伸缩性。并在Netty,Undertow和Servlet 3.1 +容器等服务器上运行。

在WebFlux中,主要的组件包括:

  • Reactor: Reactor是WebFlux底层使用的响应式编程库,提供了MonoFlux这两种响应式类型,分别用于表示0-1个和0-N个异步序列元素。
  • WebHandler: 是处理请求的核心接口,所有的请求都会被分配给一个WebHandler来处理。
  • HandlerMapping: 用于将请求映射到对应的WebHandler
  • HandlerAdapter: 用于适配WebHandler的执行,使其能够处理请求并返回响应。
  • WebFilter: 类似于Servlet中的Filter,可以在请求处理前后进行拦截和处理。
  • ServerResponseServerRequest: 分别代表HTTP的响应和请求,在WebFlux中用于处理非阻塞的请求和响应。
  • RouterFunction: 用于声明式地定义路由规则,将请求映射到处理器函数。

下面是WebFlux的基本流程图:

Request
Dispatch
Map to
Filter
Process
Response
Return
Use
Async Processing
Client
Server
HandlerMapping
WebHandler
WebFilter
HandlerAdapter
ServerResponse
Reactor

在这个流程中:

  1. 客户端发送请求到服务器。
  2. 服务器接收到请求,并将其分发给HandlerMapping
  3. HandlerMapping根据请求信息将其映射到对应的WebHandler
  4. WebFilter可以在请求到达WebHandler之前或之后进行拦截和处理。
  5. WebHandler处理请求,可能会使用Reactor库中的MonoFlux进行异步处理。
  6. HandlerAdapterWebHandler的处理结果适配成服务器可以发送的响应。
  7. ServerResponse将响应返回给客户端。

WebFlux基础实战练习

0. WebFlun环境准备

基于Spring Boot,项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.6</version>
    </parent>

    <artifactId>chapter04-webflux</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-bom</artifactId>
                <version>2023.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot Starter Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project>

启动类

@SpringBootApplication
public class WebFluxMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebFluxMainApplication.class,args);
    }
}

1. 基于注解的编程模式

WebFlux提供了两种主要的编程模式,分别是基于注解的编程模式(Annotation-based Programming)和函数式编程模式(Functional Programming)。

基于注解的编程模式(Annotation-based Programming): 这是类似于传统的Spring MVC风格的编程模式。开发者可以使用注解来定义Controller、请求映射、参数绑定等,类似于Spring MVC的@Controller和@RequestMapping注解。使用这种模式,开发者可以通过注解轻松地定义和配置应用程序的各个组件。

WebFlux基于注解的编程模式的工作流程图:
请求到达
注解映射
业务逻辑处理
响应构建
客户端请求
Controller
RequestMapping
Mono
客户端
代码示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class WebFluxController {

    @GetMapping("/mono")
    public Mono<String> monoExample() {
        return Mono.just("Hello, Mono!");
    }

    @GetMapping("/flux")
    public Flux<Integer> fluxExample() {
        return Flux.just(1, 2, 3, 4, 5);
    }
}

测试

GET http://localhost:8080/mono 返回响应:Hello, Mono!

GET http://localhost:8080/flux 返回响应:[1,2,3,4,5]

相关API
  1. Mono

    • Mono 是 Reactor 中表示包含零个或一个元素的响应式类型。
    • 适用于表示异步操作的结果,例如从数据库查询、网络调用或其他异步任务中返回的单个结果。
    • 可以通过 Mono.just(value) 创建包含单个值的 Mono,也可以通过各种操作符对 Mono 进行组合、转换和操作。
    • Mono 可以表示一个成功的结果,也可以表示错误或空值。
  2. Flux

    • Flux 是 Reactor 中表示包含零个、一个或多个元素的响应式类型。
    • 适用于表示异步流的结果,例如从事件流、消息队列或其他异步源中返回的多个元素。
    • 可以通过 Flux.just(value1, value2, ...) 创建包含多个值的 Flux,也可以通过各种操作符对 Flux 进行处理和转换。
    • Flux 支持背压(backpressure),可以有效地处理大量的异步数据。

在使用 WebFlux 框架时,MonoFlux 通常用于表示响应的内容。Mono 表示单一值的响应,而 Flux 表示包含多个值的响应,适用于处理异步请求和构建响应式应用程序。

2. 函数式编程模式

函数式编程模式(Functional Programming): 这是WebFlux的另一种编程模式,它使用Router和Handler函数。开发者通过编写函数式的路由和处理器来定义请求的处理逻辑,而不是使用注解。这种模式更加灵活,并且适用于需要更直观、函数式风格的代码。

WebFlux基于函数式编程模式的工作流程图:
请求到达
路由
业务逻辑处理
响应构建
客户端请求
RouterFunction
HandlerFunction
ServerResponse
客户端
代码示例
@Configuration
public class WebFluxRouter {

    @Bean
    public RouterFunction<ServerResponse> routeExample(WebFluxHandler handler) {
        return route()
                .GET("/router/mono", handler::monoExample)
                .GET("/router/flux", handler::fluxExample)
                .build();
    }
}

@Component
class WebFluxHandler {

    public Mono<ServerResponse> monoExample(ServerRequest request) {
        return ok().contentType(MediaType.TEXT_PLAIN)
                .bodyValue("Hello, Mono from Router!");
    }

    public Mono<ServerResponse> fluxExample(ServerRequest request) {
        return ok().contentType(MediaType.APPLICATION_JSON)
                .body(Flux.just(1, 2, 3, 4, 5).collectList(), List.class);
    }

}

测试

GET http://localhost:8080/router/mono 返回响应:Hello, Mono from Router!

GET http://localhost:8080/flux 返回响应:[1,2,3,4,5]

相关API
  1. RouterFunction<ServerResponse>
    • RouterFunction 是 Spring WebFlux 中用于定义路由的函数接口。
    • RouterFunction<ServerResponse> 表示这个函数定义了一组路由规则,每个路由规则都映射到一个处理器函数,用于处理特定的HTTP请求。
    • 在这个例子中,routeExample 方法创建了一个路由函数,定义了两个GET请求的路由规则,分别映射到 "/router/mono""/router/flux" 路径,并指定了相应的处理器函数。
  2. ServerRequest
    • ServerRequest 是 Spring WebFlux 中表示HTTP请求的对象。
    • 在处理器函数中,ServerRequest 包含了与请求相关的信息,例如HTTP方法、路径、请求头等。
    • 在这个例子中,monoExamplefluxExample 方法的参数中都包含了一个 ServerRequest 对象,用于处理相关的请求信息。
  3. ServerResponse
    • ServerResponse 是 Spring WebFlux 中表示HTTP响应的对象。
    • 通过 ServerResponse,可以设置响应的状态码、头部信息、内容类型等,并定义响应体。
    • 在这个例子中,monoExamplefluxExample 方法的返回类型是 Mono<ServerResponse>,表示响应是一个响应式单值。
  4. MediaType
    • MediaType 是 Spring 框架中表示媒体类型的枚举,用于指定HTTP请求和响应的媒体类型。
    • 在这个例子中,MediaType.TEXT_PLAIN 表示文本媒体类型,而 MediaType.APPLICATION_JSON 表示JSON媒体类型。
  5. MonoFlux
    • MonoFlux 是 Reactor 框架中的类型,用于处理响应式编程和异步数据流。
    • 在这个例子中,monoExample 方法返回的是一个 Mono<ServerResponse>,而 fluxExample 方法返回的是一个 Mono<ServerResponse>,表示响应是异步单值或异步数据流。
  6. .bodyValue("Hello, Mono from Router!")
    • .bodyValueServerResponse 中的方法,用于设置响应体的值。
    • 在这个例子中,通过 .bodyValue("Hello, Mono from Router!") 设置了响应体的值为字符串 “Hello, Mono from Router!”。
    • 这是一个简单的方式,适用于需要返回一个确定的值作为响应体的情况。
  7. .body(Flux.just(1, 2, 3, 4, 5).collectList(), List.class)
    • .body 方法用于设置响应体,与 .bodyValue 不同,.body 可以处理更复杂的情况,例如异步数据流。
    • 在这个例子中,使用了 .body(Flux.just(1, 2, 3, 4, 5).collectList(), List.class),表示响应体是一个包含整数 1, 2, 3, 4, 5 的 Flux,通过 collectList() 转换为 Mono<List<Integer>>
    • List.class 参数提供了响应体的元素类型信息。

这些API和注解共同构建了一个基于WebFlux的响应式路由和处理器。路由定义通过 RouterFunction 创建,处理逻辑通过 WebFluxHandler 中的方法实现,而 MonoFlux 用于表示异步的响应。

3. 异常处理和全局错误处理

代码示例
@ControllerAdvice
public class WebFluxExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Mono<ServerResponse> handleException(Exception ex) {
        System.out.println("有错不改 ex = " + ex);
        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(BodyInserters.fromValue("Error: 系统繁忙!"));
    }
}
  • TODO:存在bug,目前拦截错误后不能正常响应"Error: 系统繁忙!"

4. WebFlux集成测试

代码示例
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebFluxIntegrationTest {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void testMonoEndpoint() {
        webTestClient.get()
                .uri("/router/mono")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello, Mono from Router!");
    }

    @Test
    void testFluxEndpoint() {
        webTestClient.get()
                .uri("/router/flux")
                .exchange()
                .expectStatus().isOk()
                .expectBodyList(Integer.class).isEqualTo(List.of(1, 2, 3, 4, 5));
    }
}

相关API
  1. @SpringBootTest 注解:

    • 该注解表示这是一个Spring Boot的集成测试。
    • webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT 表示在测试过程中随机选择一个可用端口。
  2. WebTestClient 对象:

    • 通过@Autowired注入WebTestClient,这是Spring框架提供的用于进行Web请求的测试客户端。
  3. testMonoEndpoint 方法:

    • 这是一个测试方法,用于测试一个返回单一值的WebFlux端点。
    • webTestClient.get() 发起一个GET请求。
    • .uri("/router/mono") 设置请求的URI为"/router/mono"。
    • .exchange() 发送请求并获取响应。
    • .expectStatus().isOk() 验证响应的HTTP状态码是否为OK(200)。
    • .expectBody(String.class).isEqualTo("Hello, Mono from Router!") 验证响应体的内容是否为指定的字符串。
  4. testFluxEndpoint 方法:

    • 这是另一个测试方法,用于测试一个返回Flux(反应式流)的WebFlux端点。
    • testMonoEndpoint类似,不同之处在于使用了.expectBodyList(Integer.class)来验证响应体是否为一个List,并且该List的元素类型为整数。
    • 使用.isEqualTo(List.of(1, 2, 3, 4, 5))验证返回的整数列表是否与期望的一致。

总体来说,这个测试类通过WebTestClient发送HTTP请求到"/router/mono"和"/router/flux"端点,然后验证返回的响应是否符合预期。这样可以确保WebFlux端点的行为是正确的,同时也是一种测试反应式流的方式。

学习打卡day09:响应式编程WebFlux基础实战练习

文章来源:https://blog.csdn.net/Demo_00/article/details/135531474
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。