响应式编程WebFlux是Spring Framework 5中引入的一个全新的响应式编程框架,它基于Reactor库构建,提供了异步和非阻塞的事件处理。WebFlux框架设计用于处理长时间运行的异步任务,例如网络调用或数据库操作,而不会阻塞线程。这样可以提高系统的吞吐量和伸缩性。并在Netty,Undertow和Servlet 3.1 +容器等服务器上运行。
在WebFlux中,主要的组件包括:
Reactor
: Reactor是WebFlux底层使用的响应式编程库,提供了Mono
和Flux
这两种响应式类型,分别用于表示0-1个和0-N个异步序列元素。WebHandler
: 是处理请求的核心接口,所有的请求都会被分配给一个WebHandler
来处理。HandlerMapping
: 用于将请求映射到对应的WebHandler
。HandlerAdapter
: 用于适配WebHandler
的执行,使其能够处理请求并返回响应。WebFilter
: 类似于Servlet中的Filter,可以在请求处理前后进行拦截和处理。ServerResponse
和ServerRequest
: 分别代表HTTP的响应和请求,在WebFlux中用于处理非阻塞的请求和响应。RouterFunction
: 用于声明式地定义路由规则,将请求映射到处理器函数。下面是WebFlux的基本流程图:
在这个流程中:
HandlerMapping
。HandlerMapping
根据请求信息将其映射到对应的WebHandler
。WebFilter
可以在请求到达WebHandler
之前或之后进行拦截和处理。WebHandler
处理请求,可能会使用Reactor
库中的Mono
或Flux
进行异步处理。HandlerAdapter
将WebHandler
的处理结果适配成服务器可以发送的响应。ServerResponse
将响应返回给客户端。基于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);
}
}
WebFlux提供了两种主要的编程模式,分别是基于注解的编程模式(Annotation-based Programming)和函数式编程模式(Functional Programming)。
基于注解的编程模式(Annotation-based Programming): 这是类似于传统的Spring MVC风格的编程模式。开发者可以使用注解来定义Controller、请求映射、参数绑定等,类似于Spring MVC的@Controller和@RequestMapping注解。使用这种模式,开发者可以通过注解轻松地定义和配置应用程序的各个组件。
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]
Mono
:
Mono
是 Reactor 中表示包含零个或一个元素的响应式类型。Mono.just(value)
创建包含单个值的 Mono
,也可以通过各种操作符对 Mono
进行组合、转换和操作。Mono
可以表示一个成功的结果,也可以表示错误或空值。Flux
:
Flux
是 Reactor 中表示包含零个、一个或多个元素的响应式类型。Flux.just(value1, value2, ...)
创建包含多个值的 Flux
,也可以通过各种操作符对 Flux
进行处理和转换。Flux
支持背压(backpressure),可以有效地处理大量的异步数据。在使用 WebFlux 框架时,Mono
和 Flux
通常用于表示响应的内容。Mono
表示单一值的响应,而 Flux
表示包含多个值的响应,适用于处理异步请求和构建响应式应用程序。
函数式编程模式(Functional Programming): 这是WebFlux的另一种编程模式,它使用Router和Handler函数。开发者通过编写函数式的路由和处理器来定义请求的处理逻辑,而不是使用注解。这种模式更加灵活,并且适用于需要更直观、函数式风格的代码。
@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]
RouterFunction<ServerResponse>
:
RouterFunction
是 Spring WebFlux 中用于定义路由的函数接口。RouterFunction<ServerResponse>
表示这个函数定义了一组路由规则,每个路由规则都映射到一个处理器函数,用于处理特定的HTTP请求。routeExample
方法创建了一个路由函数,定义了两个GET请求的路由规则,分别映射到 "/router/mono"
和 "/router/flux"
路径,并指定了相应的处理器函数。ServerRequest
:
ServerRequest
是 Spring WebFlux 中表示HTTP请求的对象。ServerRequest
包含了与请求相关的信息,例如HTTP方法、路径、请求头等。monoExample
和 fluxExample
方法的参数中都包含了一个 ServerRequest
对象,用于处理相关的请求信息。ServerResponse
:
ServerResponse
是 Spring WebFlux 中表示HTTP响应的对象。ServerResponse
,可以设置响应的状态码、头部信息、内容类型等,并定义响应体。monoExample
和 fluxExample
方法的返回类型是 Mono<ServerResponse>
,表示响应是一个响应式单值。MediaType
:
MediaType
是 Spring 框架中表示媒体类型的枚举,用于指定HTTP请求和响应的媒体类型。MediaType.TEXT_PLAIN
表示文本媒体类型,而 MediaType.APPLICATION_JSON
表示JSON媒体类型。Mono
和 Flux
:
Mono
和 Flux
是 Reactor 框架中的类型,用于处理响应式编程和异步数据流。monoExample
方法返回的是一个 Mono<ServerResponse>
,而 fluxExample
方法返回的是一个 Mono<ServerResponse>
,表示响应是异步单值或异步数据流。.bodyValue("Hello, Mono from Router!")
:
.bodyValue
是 ServerResponse
中的方法,用于设置响应体的值。.bodyValue("Hello, Mono from Router!")
设置了响应体的值为字符串 “Hello, Mono from Router!”。.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
中的方法实现,而 Mono
和 Flux
用于表示异步的响应。
@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: 系统繁忙!"));
}
}
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));
}
}
@SpringBootTest
注解:
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
表示在测试过程中随机选择一个可用端口。WebTestClient
对象:
@Autowired
注入WebTestClient
,这是Spring框架提供的用于进行Web请求的测试客户端。testMonoEndpoint
方法:
webTestClient.get()
发起一个GET请求。.uri("/router/mono")
设置请求的URI为"/router/mono"。.exchange()
发送请求并获取响应。.expectStatus().isOk()
验证响应的HTTP状态码是否为OK(200)。.expectBody(String.class).isEqualTo("Hello, Mono from Router!")
验证响应体的内容是否为指定的字符串。testFluxEndpoint
方法:
testMonoEndpoint
类似,不同之处在于使用了.expectBodyList(Integer.class)
来验证响应体是否为一个List
,并且该List
的元素类型为整数。.isEqualTo(List.of(1, 2, 3, 4, 5))
验证返回的整数列表是否与期望的一致。总体来说,这个测试类通过WebTestClient
发送HTTP请求到"/router/mono"和"/router/flux"端点,然后验证返回的响应是否符合预期。这样可以确保WebFlux端点的行为是正确的,同时也是一种测试反应式流的方式。
学习打卡day09:响应式编程WebFlux基础实战练习