知识文档
WebFlux 简介
-
WebFlux 是什么 Spring 5 引入的响应式 Web 框架,基于 Reactor(Mono / Flux) ,支持 异步非阻塞,适合高并发场景。
-
核心特性
- 基于 Reactive Streams 规范
- 核心容器使用 Netty / Undertow / Servlet 3.1+ 容器
- 核心对象:
Mono<T>
(0-1 个元素)、Flux<T>
(0-N 个元素)

Hello word:
1️⃣ pom.xml
依赖(Maven)
xml
<dependencies>
<!-- Spring Boot WebFlux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Lombok (可选,用于简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2️⃣ 启动类 Application.java
typescript
package com.example.hellowebflux;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3️⃣ Controller 示例 HelloController.java
kotlin
package com.example.hellowebflux.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {// 返回响应式 Mono
return Mono.just("Hello, WebFlux!");
}
}
handler案例:
kotlin
package com.example.webfluxhandler.handler;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class HelloHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("Hello from HandlerFunction!");
}
public Mono<ServerResponse> greet(ServerRequest request) {
String name = request.pathVariable("name");
return ServerResponse.ok()
.bodyValue("Hello, " + name + "!");
}
}
package com.example.webfluxhandler.router;
import com.example.webfluxhandler.handler.HelloHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
@Configuration
public class HelloRouter {
@Bean
public RouterFunction<ServerResponse> router(HelloHandler helloHandler) {
return route(GET("/hello"), helloHandler::hello)
.andRoute(GET("/greet/{name}"), helloHandler::greet);
}
}
4️⃣ 运行
- 启动
Application
类。 - 访问浏览器或 Postman:
http://localhost:8080/hello
- 响应会返回:
Hello, WebFlux!
响应式基础概念
2.1 核心类型
-
Mono
- 表示 0 或 1 个元素
- 类似
Optional<T>
或CompletableFuture<T>
-
Flux
- 表示 0 到 N 个元素
- 类似
Stream<T>
2.2 常用操作
-
Mono.just(value)
/Flux.just(...)
-
Mono.empty()
/Flux.empty()
-
Mono.defer(() -> ...)
:延迟执行 -
转换操作(Transforming)用于对流中的数据进行转换:
- map(Function<T,R>) :元素逐个映射(同步转换)。
- flatMap(Function<T,Publisher>) :将元素映射成新的 Publisher,再展开合并。
- concatMap(Function<T,Publisher>) :和 flatMap 类似,但保证顺序。
- filter(Predicate) :过滤掉不符合条件的元素。
- distinct() :去重。
- take(n) :取前 n 个元素。
- skip(n) :跳过前 n 个元素。
-
组合操作(Combining)用于组合多个 Publisher:
- concat/concatWith:顺序连接多个流。
- merge/mergeWith:并发合并多个流(不保证顺序)。
- zip:按位置合并多个流,打包成 Tuple。
- startWith:在流前面追加元素。
-
错误处理操作(Error Handling)WebFlux 强调 信号流,错误也是信号:
- onErrorReturn(value) :出错时返回默认值。
- onErrorResume(Function<Throwable,Publisher>) :出错时切换到备用流。
- onErrorContinue:忽略异常并继续。
- retry(n) :失败后重试 n 次。
-
终止操作(Terminal)只有调用了终止操作,Publisher 才会执行:
- subscribe() :订阅触发执行。
- block() / blockOptional() :阻塞获取结果(不推荐 WebFlux 中大量使用)。
- collectList() :把 Flux 收集成一个 List。
- collectMap() :收集成 Map。
- then() :忽略数据,只关心完成信号。
- 上下文操作(Context)Reactor 提供类似
ThreadLocal
的上下文: - contextWrite(Context -> Context) :写入上下文数据。
- deferContextual(ctx -> ...) :读取上下文数据。
-
调度(Scheduler)控制执行线程:
-
publishOn(scheduler) :切换后续操作执行的线程池。
-
subscribeOn(scheduler) :切换上游执行的线程池。
-
常见调度器:
Schedulers.parallel()
:并行线程池。Schedulers.boundedElastic()
:适合阻塞调用。Schedulers.single()
:单线程。
-
-
WebFlux 特有操作 结合 Spring WebFlux 常用:
- ServerRequest/ServerResponse:函数式路由 API。
- RouterFunction/HandlerFunction:代替传统 Controller。
- BodyInserters.fromPublisher() :返回 Mono/Flux 到响应体。
- ServerResponse.ok().body(Publisher,Class) :响应 Publisher。
📌 Reactor 里的延时执行
举个例子:
ini
Mono<String> mono = Mono.just("hello");
这行代码已经在内存里立刻创建了一个 "hello"
,不会延时。 而如果你写成:
go
Mono<String> mono = Mono.defer(() -> {
System.out.println("执行逻辑");return Mono.just("hello");
});
这里的 System.out.println("执行逻辑")
不会立即执行 ,只有在 mono.subscribe()
的时候才执行。
📌 那么 mono.subscribe()
什么时候会执行?
你手动调用 .subscribe()
scss
Mono.just("hello")
.doOnNext(System.out::println)
.subscribe(); // ← 这里会触发执行
手动调用 .subscribe()
适合:
- 单元测试
- 工具类里临时执行
- 独立的非 WebFlux 环境(比如 main 方法里)
在 WebFlux Controller 里返回 Mono/Flux
typescript
@GetMapping("/hello")
public Mono<String> hello() {return Mono.just("hello webflux");
}
这里你没写 .subscribe()
,但请求进来后, ⚡ WebFlux 底层 DispatcherHandler 会帮你调用 subscribe 。 所以 Controller 里 只需要返回 Mono/Flux
,不用手动订阅。
在 WebFilter
、HandlerFilterFunction
等过滤器里
同样的,Spring WebFlux 内部会在响应阶段统一 subscribe()
, 只要你返回 Mono<Void>
或 Mono<ResponseEntity<...>>
,它会在链路完成时自动订阅。
在响应式流中(操作符触发)
如果你用 then() / flatMap()
等组合操作符,它们内部也会订阅上游流。
例子:
ini
Mono<Void> mono = Mono.just("hello")
.doOnNext(System.out::println)
.then(); // then 内部会触发订阅上游
Scheduler / 异步任务里
比如用 publishOn(Schedulers.boundedElastic())
或 subscribeOn(...)
, 当线程调度器调度时,也会触发订阅。
⚠️ 注意点
-
在 Controller / Service / Filter 中 不要手动调用 subscribe() , 否则会 提前触发执行,而 WebFlux 框架再执行时就可能出问题(两次订阅、空 body、异常丢失)。
-
手动
.subscribe()
只适合独立执行的场景,不适合在请求链里用。 -
转换
map
:同步一对一转换flatMap
:异步转换,返回新的Mono
/Flux
filter
:过滤元素defaultIfEmpty
:空值时提供默认值
-
错误处理
onErrorResume(e -> fallbackMono)
onErrorReturn(defaultValue)
doOnError(e -> log.error("错误", e))
📌 subscribe 可以重复吗?
可以重复 subscribe,但要分情况:
Cold Publisher(冷流) → 每次订阅都会"重新执行"
例如:
kotlin
Mono<String> mono = Mono.fromSupplier(() -> {
System.out.println("执行逻辑");return "hello";
});
mono.subscribe(System.out::println); // 第一次订阅
mono.subscribe(System.out::println); // 第二次订阅
输出:
执行逻辑
hello
执行逻辑
hello
解释:
Mono.fromSupplier
、Mono.just
、Flux.just
这种是 cold 的。- 每次订阅 = 从头再来一次。
- 所以适合放置"可以重复"的逻辑(查询 DB、调用接口等)。
Hot Publisher(热流) → 多个订阅者共享同一个数据流
例如:
ini
Flux<Long> flux = Flux.interval(Duration.ofSeconds(1)).share();
flux.subscribe(v -> System.out.println("订阅A: " + v));
Thread.sleep(3000);
flux.subscribe(v -> System.out.println("订阅B: " + v));
输出可能是:
makefile
订阅A: 0
订阅A: 1
订阅A: 2
订阅B: 2 // B 从当前时间点接收,不会从头开始
订阅A: 3
订阅B: 3
解释:
Flux.interval
是一个 hot stream,会持续产生数据。share()
后,多个订阅者共享同一个流。- 新的订阅者不会重放历史数据,只能接收"后续"的。
Mono 的特殊情况
Mono.just("value")
→ 每次订阅返回同一个值,可以重复订阅没问题。Mono.fromCallable(...)
→ 每次订阅都会重新执行 Callable。Mono.cache()
→ 缓存结果,后续订阅不再重新执行。
例子:
ini
Mono<String> cached = Mono.fromSupplier(() -> {
System.out.println("执行一次");return "hello";
}).cache();
cached.subscribe(System.out::println);
cached.subscribe(System.out::println);
输出:
arduino
执行一次
hello
hello // 第二次没有再执行逻辑
⚠️ 注意点
-
WebFlux Controller 返回的 Mono/Flux 是冷流,所以每次请求都会重新执行,这是符合预期的。
-
如果你希望只执行一次并复用结果 → 用
.cache()
。 -
如果你想广播给多个订阅者 → 用
.share()
或.publish().refCount(...)
。
WebFlux Controller 基础写法
less
@RestController
@RequestMapping("/devices")
public class DeviceController {
@GetMapping("/{id}")
public Mono<Result<Device>> getDevice(@PathVariable Long id) {return deviceService.findById(id)
.map(Result::ok)
.defaultIfEmpty(Result.error("设备不存在"))
.onErrorResume(e -> {
log.error("查询设备异常", e);return Mono.just(Result.error("查询失败: " + e.getMessage()));
});
}
@GetMapping("/list")
public Flux<Device> listDevices() {return deviceService.findAll();
}
}
Filter / 拦截器
WebFlux 没有 HandlerInterceptor
,而是使用 WebFilter
:
less
@Component
@Order(-1)
public class AuthFilter implements WebFilter {@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {String token = exchange.getRequest().getHeaders().getFirst("Authorization");if (token == null) {return Mono.error(new BusinessException("未登录"));
}
return chain.filter(exchange)
.contextWrite(ctx -> ctx.put("auth.token", token)); // 存入 Reactor 上下文
}
}
在业务代码里取上下文:
go
// 从上下文里取出用户信息
return IdeamakeSubjectContext.get()
.flatMap(user -> {
log.info("当前用户信息: {}", user);
// 继续执行业务逻辑
return deviceService.getDeviceById(id)
.map(Result::ok)
.defaultIfEmpty(Result.error("设备不存在"));
})
.onErrorResume(error -> {
log.error("获取设备详情失败,ID: {}", id, error);
return Mono.just(Result.error(STR."获取设备详情失败: {error.getMessage()}"));
});
Reactor 上下文(Context)
- 作用:跨层传递用户信息、追踪 ID、日志链路等。
- 特点 :类似
ThreadLocal
,但是支持异步非阻塞。 - 写入 :
.contextWrite(ctx -> ctx.put("key", value))
- 读取 :
Mono.deferContextual(ctx -> Mono.just(ctx.get("key")))
异常处理
全局异常处理
java
package cn.ideamake.aidee.exception;
import cn.ideamake.common.response.Result;
import cn.ideamake.common.response.ResultEnum;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* @author Barcke
* @version 1.0
* @projectName aidee
* @className GlobalExceptionHandler
* @date 2025/9/10 14:17
* @slogan: 源于生活 高于生活
* @description:
**/
@Slf4j
@Component
@Order(-2) // 保证优先级高于默认异常处理
@RequiredArgsConstructor
public class GlobalExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
// 已经提交响应就不再处理
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
// 构造返回体
Result<Object> result;
if (ex instanceof BusinessException e) {
// 业务异常
String stack = Arrays.stream(e.getStackTrace())
.limit(10)
.map(StackTraceElement::toString)
.collect(Collectors.joining("\n"));
log.error("已知异常-BusinessException: msg={} stack={}", e.getMsg(), stack);
result = Result.error(e.getCode(), e.getMsg());
} else {
// 未知异常
log.error("全局异常: {}", ex.getMessage(), ex);
result = Result.error(ResultEnum.FAIL.getCode(), "出错了,请联系管理员!");
}
try {
// 设置响应头(避免 ReadOnlyHttpHeaders 问题)
exchange.getResponse().getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
byte[] bytes = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
return exchange.getResponse()
.writeWith(Mono.just(exchange.getResponse()
.bufferFactory()
.wrap(bytes)));
} catch (Exception e) {
log.error("处理异常响应时发生错误", e);
return Mono.error(ex);
}
}
}
-
局部异常处理
- 使用
onErrorResume
/onErrorReturn
响应式编程常见坑
-
避免阻塞
- 禁止使用
block()
/subscribe()
在 Controller 里 - 数据库/Redis/HTTP 调用必须使用响应式客户端(R2DBC, WebClient, reactive-redis)
- 禁止使用
-
线程模型
- 默认使用
reactor-netty
事件循环线程 - 如果有阻塞操作,用
publishOn(Schedulers.boundedElastic())
- 默认使用
-
上下文传递
- 不要用 ThreadLocal,使用
Reactor Context
- 不要用 ThreadLocal,使用
📌 总结
WebFlux 基础开发需要掌握:
1. 核心概念
- 响应式、非阻塞、异步。
- 核心类型:
Mono<T>
(0/1个元素)、Flux<T>
(0-N个元素)。 - 基于 Reactor + Netty 事件循环模型。
2. 开发模式
- 注解式 :
@RestController
+Mono/Flux
返回。 - 函数式 :
RouterFunction
+HandlerFunction
,更灵活。
3. 常用操作
- 创建:
Mono.just()
、Flux.fromIterable()
- 转换:
map
(同步)、flatMap
(异步) - 过滤/条件:
filter
、switchIfEmpty
- 错误处理:
onErrorResume
、doOnError
- 组合操作:
zip
、merge
、concat
4. 上下文与状态
- 使用
Context
或自定义SubjectContext
保存请求级信息。 - 避免阻塞调用和 ThreadLocal。
5. 注意事项
- 所有阻塞操作需在
Schedulers.boundedElastic()
中执行。 Mono/Flux
只有订阅 (subscribe
) 才会执行。- 错误必须捕获,否则会触发
onErrorDropped
异常。
6. 总结一句话
WebFlux 是一个基于 Reactor 的响应式框架,核心是非阻塞、异步和背压,适合高并发、流式数据处理场景。