Spring Boot: Spring WebFlux 响应式编程
最后更新:2026-08-26
WebFlux 是 Spring 的异步引擎——Mono 一个、Flux 一流,非阻塞 I/O 应对万级并发。
1. 你将学到
- 响应式编程范式:Mono / Flux 核心概念与操作符
@RestController+Mono<T>/Flux<T>响应式接口编写- WebFlux vs WebMVC 适用场景对比与选型指南
- Reactive Repositories(Spring Data R2DBC)
- Alice 实现每秒处理千级订单查询的响应式 API
2. 一个高并发架构师的真实故事
(1) 痛点:线程池耗尽
Alice 的 OrderFlow 在大促时遇到瓶颈——500 并发用户查询订单,Tomcat 线程池(默认 200 线程)耗尽,请求排队等待,P99 延迟飙到 3 秒。线程大部分时间在等 I/O(数据库、网络),CPU 利用率只有 15%。Bob 建议加线程数,但每增加 1000 线程就多消耗 1 GB 内存。
(2) WebFlux 的解法
WebFlux 用少量线程处理大量并发——I/O 等待时线程不阻塞,去服务其他请求:
JAVA
@GetMapping("/{id}")
public Mono<OrderResponse> getOrder(@PathVariable Long id) {
return orderRepository.findById(id)
.map(OrderResponse::from)
.switchIfEmpty(Mono.error(new ResourceNotFoundException("Order", id)));
}
(3) 收益
Alice 用 WebFlux 重构查询接口后,4 个线程支撑 2000 并发,内存占用降低 70%,P99 延迟从 3 秒降到 100ms,CPU 利用率提升到 60%。
3. 响应式编程核心概念
(1) Mono 和 Flux
| 类型 | 含义 | 类比 | 示例 |
|---|---|---|---|
Mono<T> |
0 或 1 个元素 | Optional 的异步版 | 查询单个订单 |
Flux<T> |
0 到 N 个元素 | Stream 的异步版 | 查询订单列表 |
Mono<Void> |
无返回值 | 完成信号 | 删除操作 |
graph LR
A["Publisher"] --> B["Mono<br/>0 or 1 element"]
A --> C["Flux<br/>0 to N elements"]
B --> D["onNext → onComplete"]
C --> E["onNext × N → onComplete"]
B --> F["onError"]
C --> F
▶ 示例: Mono 和 Flux 基本操作
JAVA
// Mono: single value
Mono<String> mono = Mono.just("Hello OrderFlow");
Mono<String> empty = Mono.empty();
Mono<String> fromCallable = Mono.fromCallable(() -> fetchOrder(1L));
// Flux: multiple values
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);
Flux<Integer> range = Flux.range(1, 100);
Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));
输出:
TEXT
📖 仅展示
// 执行成功
(2) 常用操作符
| 操作符 | 作用 | 示例 |
|---|---|---|
map |
转换元素 | .map(OrderResponse::from) |
flatMap |
异步转换 | .flatMap(this::enrichOrder) |
filter |
过滤 | .filter(order -> "PENDING".equals(order.getStatus())) |
switchIfEmpty |
空值替代 | .switchIfEmpty(Mono.error(...)) |
onErrorResume |
错误降级 | .onErrorResume(e -> fallbackOrder()) |
timeout |
超时控制 | .timeout(Duration.ofSeconds(5)) |
retry |
重试 | .retry(3) |
subscribeOn |
指定订阅线程 | .subscribeOn(Schedulers.boundedElastic()) |
▶ 示例: 操作符链式调用
JAVA
public Mono<OrderResponse> getOrderWithDetails(Long id) {
return orderRepository.findById(id)
.flatMap(order ->
productRepository.findById(order.getProductId())
.map(product -> OrderResponse.from(order, product)))
.switchIfEmpty(Mono.error(
new ResourceNotFoundException("Order", id)))
.timeout(Duration.ofSeconds(3))
.onErrorResume(TimeoutException.class,
e -> Mono.error(new RuntimeException("Order query timed out")))
.retry(2);
}
输出:
TEXT
📖 仅展示
// 执行成功
4. WebFlux REST API
▶ 示例: 响应式 Controller
JAVA
@RestController
@RequestMapping("/api/v1/reactive/orders")
public class ReactiveOrderController {
private final ReactiveOrderService orderService;
public ReactiveOrderController(ReactiveOrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public Mono<OrderResponse> getOrder(@PathVariable Long id) {
return orderService.findById(id);
}
@GetMapping
public Flux<OrderResponse> listOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return orderService.findAll(page, size);
}
@PostMapping
public Mono<ResponseEntity<OrderResponse>> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
return orderService.create(request)
.map(order -> ResponseEntity
.status(HttpStatus.CREATED).body(order));
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> cancelOrder(@PathVariable Long id) {
return orderService.cancel(id)
.then(Mono.just(ResponseEntity.noContent().<Void>build()));
}
}
输出:
TEXT
📖 仅展示
// 执行成功
| 维度 | WebMVC | WebFlux |
|---|---|---|
| 返回类型 | 同步对象 | Mono<T> / Flux<T> |
| 线程模型 | 一请求一线程 | 事件循环(少量线程) |
| I/O 模型 | 阻塞 | 非阻塞 |
| 容器 | Tomcat / Jetty | Netty |
| 并发上限 | 线程池大小 | 几乎无上限 |
5. WebFlux vs WebMVC 选型
(1) 选型决策
| 场景 | 推荐 | 原因 |
|---|---|---|
| CRUD API,并发 < 500 | WebMVC | 简单直观,生态成熟 |
| I/O 密集型,高并发 | WebFlux | 非阻塞,少量线程支撑大量并发 |
| 实时流(SSE/WebSocket) | WebFlux | 原生支持 |
| 大量阻塞操作(JDBC) | WebMVC | WebFlux 中阻塞操作反而更差 |
| 混合场景 | WebMVC + 部分异步 | 渐进式优化 |
⚠️ 注意: WebFlux 不是万能药。如果你的代码大量使用阻塞 I/O(如 JDBC),WebFlux 反而比 WebMVC 更差。选择 WebFlux 意味着整个调用链都必须是非阻塞的。
▶ 示例: Server-Sent Events (SSE)
JAVA
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<OrderEvent> streamOrderEvents() {
return orderEventPublisher.eventStream()
.log("order-events");
}
输出:
TEXT
📖 仅展示
// 执行成功
6. Spring Data R2DBC
(1) 响应式数据库访问
▶ 示例: R2DBC Entity 和 Repository
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-mysql</artifactId>
</dependency>
输出:
TEXT
📖 仅展示
// 执行成功
JAVA
@Table("products")
public class Product {
@Id
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
public Product() {}
public Product(String name, BigDecimal price, Integer stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
// getters
}
public interface ReactiveProductRepository
extends ReactiveCrudRepository<Product, Long> {
Flux<Product> findByNameContaining(String keyword);
Flux<Product> findByStockLessThan(Integer threshold);
}
| 维度 | JPA + JDBC | R2DBC |
|---|---|---|
| 驱动模型 | 阻塞 | 非阻塞 |
| API 风格 | 同步 | 响应式(Mono/Flux) |
| 关系映射 | 支持(@OneToMany) | 不支持 |
| 查询语言 | JPQL | 原生 SQL / 方法名派生 |
| 事务 | @Transactional |
@Transactional(Reactive) |
▶ 示例: 响应式 Service
JAVA
@Service
public class ReactiveOrderService {
private final ReactiveOrderRepository orderRepository;
private final ReactiveProductRepository productRepository;
public ReactiveOrderService(ReactiveOrderRepository orderRepository,
ReactiveProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
public Mono<OrderResponse> create(CreateOrderRequest request) {
return productRepository.findById(request.productId())
.switchIfEmpty(Mono.error(
new ResourceNotFoundException("Product", request.productId())))
.flatMap(product -> {
if (product.getStock() < request.quantity()) {
return Mono.error(new InsufficientStockException(
product.getId(), product.getStock(), request.quantity()));
}
product.deductStock(request.quantity());
return productRepository.save(product)
.then(orderRepository.save(new Order(product.getId(), request.quantity())));
})
.map(OrderResponse::from);
}
public Flux<OrderResponse> findAll(int page, int size) {
return orderRepository.findAll()
.skip(page * size)
.take(size)
.map(OrderResponse::from);
}
public Mono<Void> cancel(Long orderId) {
return orderRepository.findById(orderId)
.flatMap(order -> {
order.setStatus("CANCELLED");
return orderRepository.save(order);
})
.then();
}
}
输出:
TEXT
📖 仅展示
// 执行成功
7. 综合示例:OrderFlow 响应式查询 API
JAVA
// ReactiveProductRepository.java
public interface ReactiveProductRepository
extends ReactiveCrudRepository<Product, Long> {
Flux<Product> findByNameContaining(String keyword);
}
// ReactiveOrderRepository.java
public interface ReactiveOrderRepository
extends ReactiveCrudRepository<Order, Long> {
Flux<Order> findByStatus(String status);
}
// ReactiveOrderController.java
@RestController
@RequestMapping("/api/v1/reactive/orders")
public class ReactiveOrderController {
private final ReactiveOrderService orderService;
public ReactiveOrderController(ReactiveOrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public Mono<OrderResponse> get(@PathVariable Long id) {
return orderService.findById(id);
}
@GetMapping
public Flux<OrderResponse> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return orderService.findAll(page, size);
}
@GetMapping("/status/{status}")
public Flux<OrderResponse> byStatus(@PathVariable String status) {
return orderService.findByStatus(status);
}
@PostMapping
public Mono<ResponseEntity<OrderResponse>> create(
@RequestBody CreateOrderRequest req) {
return orderService.create(req)
.map(o -> ResponseEntity.status(HttpStatus.CREATED).body(o));
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> cancel(@PathVariable Long id) {
return orderService.cancel(id)
.thenReturn(ResponseEntity.noContent().<Void>build());
}
}
// ReactiveOrderService.java
@Service
public class ReactiveOrderService {
private final ReactiveOrderRepository orderRepo;
private final ReactiveProductRepository productRepo;
public ReactiveOrderService(ReactiveOrderRepository orderRepo,
ReactiveProductRepository productRepo) {
this.orderRepo = orderRepo;
this.productRepo = productRepo;
}
public Mono<OrderResponse> findById(Long id) {
return orderRepo.findById(id)
.map(OrderResponse::from)
.switchIfEmpty(Mono.error(new ResourceNotFoundException("Order", id)));
}
public Flux<OrderResponse> findAll(int page, int size) {
return orderRepo.findAll().skip((long) page * size).take(size)
.map(OrderResponse::from);
}
public Flux<OrderResponse> findByStatus(String status) {
return orderRepo.findByStatus(status).map(OrderResponse::from);
}
public Mono<OrderResponse> create(CreateOrderRequest req) {
return productRepo.findById(req.productId())
.switchIfEmpty(Mono.error(new ResourceNotFoundException("Product", req.productId())))
.flatMap(p -> {
if (p.getStock() < req.quantity()) {
return Mono.error(new InsufficientStockException(p.getId(), p.getStock(), req.quantity()));
}
p.deductStock(req.quantity());
return productRepo.save(p)
.then(orderRepo.save(new Order(p.getId(), req.quantity())));
})
.map(OrderResponse::from);
}
public Mono<Void> cancel(Long id) {
return orderRepo.findById(id)
.flatMap(o -> { o.setStatus("CANCELLED"); return orderRepo.save(o); })
.then();
}
}
❓ 常见问题
Q WebFlux 能完全替代 WebMVC 吗?
A 不建议。WebMVC 生态更成熟,绝大多数 CRUD 项目用 WebMVC 更简单。WebFlux 适合 I/O 密集、高并发、实时流的场景。两者可以共存,但不要混用(不要在 WebFlux 中用阻塞代码)。
Q Mono 和 CompletableFuture 有什么区别?
A Mono 是冷源(lazy,订阅时才执行),CompletableFuture 是热源(创建时就开始执行)。Mono 支持背压(backpressure)和取消,CompletableFuture 不支持。
Q R2DBC 支持 @OneToMany 关系映射吗?
A 不支持。R2DBC 是轻量级的响应式数据库驱动,不做关系映射。需要关联查询时,手动使用 flatMap 组合多个查询。
Q 在 WebFlux 中调用阻塞代码怎么办?
A 用
Mono.fromCallable() + subscribeOn(Schedulers.boundedElastic()) 将阻塞操作调度到弹性线程池。但频繁使用说明不适合 WebFlux。Q WebFlux 的错误处理怎么做?
A 1)操作符
onErrorResume / onErrorReturn;2)全局 @ExceptionHandler(WebFlux 也支持);3)onErrorMap 转换异常类型。Q WebFlux 如何测试?
A 使用
WebTestClient 替代 MockMvc。@WebFluxTest 加载 WebFlux 层。StepVerifier 用于测试 Mono/Flux 的元素序列。📖 小节
- Mono(0/1 元素)和 Flux(0-N 元素)是响应式编程的核心类型
- 操作符链式调用:map、flatMap、filter、switchIfEmpty、timeout、retry
- WebFlux 用 Netty + 事件循环,少量线程支撑大量并发
- WebFlux 适合 I/O 密集高并发场景,不适合大量阻塞操作
- R2DBC 是响应式数据库驱动,不支持关系映射,需手动 flatMap 组合
- WebMVC 和 WebFlux 不应混用,根据场景选择其一
📝 作业
-
基础题(难度⭐):使用 WebFlux 实现商品查询响应式 API(
GET /api/v1/reactive/products/{id}),用 R2DBC 连接 H2 内存数据库。 -
进阶题(难度⭐⭐):实现响应式下单接口,包含库存检查、扣减、创建订单的完整流程,使用
flatMap组合多个响应式操作。 -
挑战题(难度⭐⭐⭐):使用
WebTestClient和StepVerifier编写响应式 API 测试,实现 SSE 实时订单事件推送接口,对比 WebMVC 和 WebFlux 在 1000 并发下的性能差异。