踏入 WebFlux 的世界:两种编程模型
掌握了 Project Reactor 的核心语法后,我们终于迎来了 Spring WebFlux 框架层面的开发。与 Spring MVC 类似,WebFlux 同样提供了两种截然不同的编程模型,开发者可以根据团队习惯和业务场景自由选择。
-
基于注解的控制器(Annotated Controllers) :这是传统 Spring MVC 开发者最熟悉的模式。你依然可以使用 @RestController、@GetMapping 等注解,最大的区别在于方法的返回值需要替换为 Mono 或 Flux。这种模式学习成本极低,适合快速迁移现有的业务代码。
java@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public Mono<User> getUser(@PathVariable String id) { // 返回一个异步的 Mono 数据流 return userService.findById(id); } } -
函数式端点(Functional Endpoints) :这是 WebFlux 引入的轻量级、纯函数式的编程模型。它将"路由(Router)"与"处理逻辑(Handler)"彻底解耦。路由负责匹配请求,Handler 负责处理请求并返回 Mono。这种模式代码更加纯粹,非常适合构建高度模块化的微服务或网关。
java// 1. 定义 Handler public Mono<ServerResponse> getUser(ServerRequest request) { String id = request.pathVariable("id"); return userService.findById(id) .flatMap(user -> ServerResponse.ok().bodyValue(user)); } // 2. 定义 Router @Bean public RouterFunction<ServerResponse> userRoutes(UserHandler handler) { return RouterFunctions.route(GET("/api/users/{id}"), handler::getUser); }
响应式 HTTP 客户端:WebClient 登场
在传统的 Spring MVC 中,我们使用 RestTemplate 进行外部 HTTP 调用,但它是一个阻塞式的客户端。在 WebFlux 的响应式世界中,官方推荐使用 WebClient 作为替代方案。WebClient 是完全非阻塞的,支持背压和流式传输。
WebClient 的创建与基础配置 :
通常我们会通过 WebClient.Builder 注入并配置全局参数(如 BaseUrl、默认请求头、超时时间等),将其注册为 Spring Bean。
java
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
发起非阻塞请求(GET / POST):
java
// GET 请求:获取单个对象
public Mono<User> fetchUser(String id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve() // 提取响应
.bodyToMono(User.class); // 将响应体转换为 Mono<User>
}
// POST 请求:提交数据
public Mono<User> createUser(User user) {
return webClient.post()
.uri("/users")
.bodyValue(user) // 传入请求体
.retrieve()
.bodyToMono(User.class);
}
避坑警告:严禁在响应式管道中调用 block()
这是初学者从传统开发转向 WebFlux 时最容易犯,也是最致命的错误!
WebClient 返回的是 Mono 或 Flux,如果你为了获取里面的数据而调用了 .block() 方法,当前线程就会被强制阻塞,直到响应返回 。在 WebFlux 默认使用的 Netty 服务器中,I/O 线程的数量是非常有限的(通常等于 CPU 核心数)。如果你在 Controller 或 Service 中使用了 block(),就会直接阻塞这些宝贵的 I/O 线程,导致整个服务器瞬间失去响应能力,并发能力甚至不如传统的 Tomcat。
最佳实践:保持全链路的响应式。永远使用 flatMap、map 等操作符来传递和处理数据,将 Mono 和 Flux 一路透传到 Controller 层,交由框架去异步订阅。
本篇小结:WebFlux 提供了注解式和函数式两种灵活的 Web 开发模式。在对外发起 HTTP 请求时,必须使用非阻塞的 WebClient,并时刻警惕 block() 方法对 I/O 线程的致命污染。
下一步预告:外部调用解决了,那数据库层呢?传统的 JDBC 和 JPA 都是阻塞的,如何在数据层也实现全链路非阻塞?下一篇笔记,我们将深入探讨 Spring Data Reactive 与 R2DBC,打通响应式数据访问的最后一公里。