什么是WebFlux?
响应式编程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
将响应返回给客户端。
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基于注解的编程模式的工作流程图:
代码示例
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 {@Beanpublic 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. 异常处理和全局错误处理
代码示例
@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 {@Autowiredprivate WebTestClient webTestClient;@Testvoid testMonoEndpoint() {webTestClient.get().uri("/router/mono").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello, Mono from Router!");}@Testvoid 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基础实战练习