什么是WebFlux?
响应式编程WebFlux是Spring Framework 5中引入的一个全新的响应式编程框架,它基于Reactor库构建,提供了异步和非阻塞的事件处理。WebFlux框架设计用于处理长时间运行的异步任务,例如网络调用或数据库操作,而不会阻塞线程。这样可以提高系统的吞吐量和伸缩性。并在Netty,Undertow和Servlet 3.1 +容器等服务器上运行。
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>
启动类
Spring Boot 有一个 WebFlux 启动器,默认情况下,启动器使用 Netty
@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基于注解的编程模式的工作流程图:
代码示例
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
-
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
表示包含多个值的响应,适用于处理异步请求和构建响应式应用程序。
2. 函数式编程模式
函数式编程模式(Functional Programming): 这是WebFlux的另一种编程模式,它使用Router和Handler函数。开发者通过编写函数式的路由和处理器来定义请求的处理逻辑,而不是使用注解。这种模式更加灵活,并且适用于需要更直观、函数式风格的代码。
WebFlux基于函数式编程模式的工作流程图:
代码示例
@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
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
用于表示异步的响应。
3. 异常处理和全局错误处理
代码示例
@Component
@Order(-2)
public class WebFluxExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof ResponseStatusException) {
return handleResponseStatusException(exchange, (ResponseStatusException) ex);
}
// Handle other types of exceptions if needed
return Mono.error(ex);
}
private Mono<Void> handleResponseStatusException(ServerWebExchange exchange, ResponseStatusException ex) {
exchange.getResponse().setStatusCode(ex.getStatusCode());
return exchange.getResponse().setComplete();
}
}
相关API
-
WebExceptionHandler接口:
org.springframework.web.server.WebExceptionHandler
是用于处理WebFlux应用程序中异常的接口。- 通过实现这个接口,你可以自定义异常处理逻辑,并在应用程序中注册它。
-
ServerWebExchange接口:
org.springframework.web.server.ServerWebExchange
表示一次HTTP请求-响应交换。- 它包含了请求和响应的所有信息,可以在处理器中使用。
-
Mono<Void>:
reactor.core.publisher.Mono
是Reactor库中的一种响应式流类型,它表示包含零或一个元素的异步序列。- 在这个例子中,
Mono<Void>
表示一个不包含元素的Mono,用于表示异步操作的完成状态。
-
ResponseStatusException类:
org.springframework.web.server.ResponseStatusException
是Spring WebFlux中的异常类,它表示特定的HTTP响应状态码和可选的原因。- 在这个例子中,我们使用它来检查是否存在特定类型的异常,并将其转换为对应的HTTP响应状态码。
-
@Component注解:
org.springframework.stereotype.Component
是Spring框架中用于声明组件的注解。- 通过将
CustomWebFluxExceptionHandler
类标记为@Component
,我们告诉Spring框架要将它识别为一个组件,并在应用程序上下文中进行管理。
-
@Order注解:
org.springframework.core.annotation.Order
是Spring框架中用于定义组件排序顺序的注解。- 在这个例子中,
@Order(-2)
表示将CustomWebFluxExceptionHandler
组件的处理顺序设置为-2,确保它在默认处理器之前执行。
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
-
@SpringBootTest
注解:- 该注解表示这是一个Spring Boot的集成测试。
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
表示在测试过程中随机选择一个可用端口。
-
WebTestClient
对象:- 通过
@Autowired
注入WebTestClient
,这是Spring框架提供的用于进行Web请求的测试客户端。
- 通过
-
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!")
验证响应体的内容是否为指定的字符串。
-
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基础实战练习