两种过滤器
WebFlux 提供两类过滤器:
| 类型 | 作用域 | 用途 |
|---|---|---|
WebFilter | 全局所有请求 | 日志、请求 ID、CORS、认证预检 |
HandlerFilterFunction | 单个 RouterFunction | 函数式路由的局部过滤 |
WebFilter:全局过滤器
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
public class LoggingWebFilter implements WebFilter, Ordered {
@Override
public Mono<Void> filter(org.springframework.web.server.ServerWebExchange exchange,
WebFilterChain chain) {
long start = System.nanoTime();
String path = exchange.getRequest().getURI().getPath();
String method = exchange.getRequest().getMethod().name();
System.out.println("[" + Thread.currentThread().getName() + "] → " + method + " " + path);
return chain.filter(exchange)
.doFinally(sig -> {
long ms = (System.nanoTime() - start) / 1_000_000;
// 下游可能没有显式设置状态码,此时 getStatusCode() 返回 null
var status = exchange.getResponse().getStatusCode();
int code = status != null ? status.value() : 200;
System.out.println("[" + Thread.currentThread().getName() + "] ← " + code + " " + path + " in " + ms + "ms");
});
}
@Override
public int getOrder() {
return -100; // 数字越小优先级越高
}
}
WebFilter 是 Spring Bean,自动注册到全局过滤链。Ordered 接口控制优先级:
| Order 值 | 优先级 | 典型用途 |
|---|---|---|
Integer.MIN_VALUE | 最先执行 | 请求 ID 生成、CORS |
-100 | 高 | 日志记录 |
0 | 默认 | 认证检查 |
100 | 低 | 业务级后置处理 |
Integer.MAX_VALUE | 最后执行 | 兜底错误处理 |
CORS 不要放在认证之后:浏览器的预检 OPTIONS 请求通常不带 Authorization,如果认证过滤器先执行,预检会直接 401,真正的跨域请求根本发不出去。把 CORS 放在高优先级,或者让认证过滤器对 OPTIONS 放行。
请求 ID 透传
请求 ID 需要在整条请求处理链中可用。WebFlux 使用 ServerWebExchange 的 attributes:
@Component
public class RequestIdFilter implements WebFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String requestId = exchange.getRequest().getHeaders().getFirst("X-Request-ID");
if (requestId == null) {
requestId = java.util.UUID.randomUUID().toString();
}
exchange.getAttributes().put("requestId", requestId);
exchange.getResponse().getHeaders().set("X-Request-ID", requestId);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // -2147483648,最先执行
}
}
在 controller 中读取:
@GetMapping("/api/status")
public Mono<Map<String, String>> status(ServerWebExchange exchange) {
String requestId = (String) exchange.getAttribute("requestId");
return Mono.just(Map.of("requestId", requestId, "status", "ok"));
}
注意:ServerWebExchange 不是线程绑定的——响应式操作可能在不同线程上执行。attributes 底层是并发 Map,跨线程读写安全,但它只挂在 exchange 对象上,不会自动进入 Reactor Context;如果下游要在 deferContextual、日志 MDC 或异步任务里读取,仍需显式传递(见第 10.3 节)。
HandlerFilterFunction:函数式路由过滤
如果只用函数式路由,可以在 RouterFunction 上加局部过滤器:
@Bean
public RouterFunction<ServerResponse> apiRoutes(OrderHandler handler) {
RouterFunction<ServerResponse> routes = RouterFunctions.route()
.GET("/api/orders", handler::list)
.GET("/api/orders/{id}", handler::get)
.build();
return routes.filter((request, next) -> {
String token = request.headers().firstHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return ServerResponse.status(401)
.bodyValue(Map.of("error", "Unauthorized"));
}
return next.handle(request);
});
}
这个过滤器只对 apiRoutes() 定义的路由生效,不影响注解 controller 的路由。
过滤顺序与短路
过滤器可以「短路」——不调用 chain.filter() 或 next.handle(),直接返回响应:
@Component
public class RateLimitFilter implements WebFilter {
private final Map<String, Long> lastRequest = new ConcurrentHashMap<>();
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// getRemoteAddress() 在测试、Unix socket 等场景可能为 null
var remote = exchange.getRequest().getRemoteAddress();
String clientIp = remote != null ? remote.getHostString() : "unknown";
long now = System.currentTimeMillis();
Long last = lastRequest.put(clientIp, now);
if (last != null && (now - last) < 100) { // 100ms 内重复请求
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().set("Content-Type", "application/json");
return exchange.getResponse()
.writeWith(Mono.just(exchange.getResponse()
.bufferFactory().wrap("{\"error\":\"Rate limited\"}".getBytes())));
}
return chain.filter(exchange);
}
}
短路时一定会触发 doFinally(如果 filter 内部用了 doFinally),但下游 handler 不会执行。
WebFilter vs HandlerFilterFunction 对比
| 维度 | WebFilter | HandlerFilterFunction |
|---|---|---|
| 注册方式 | @Component Bean | .filter() 链式调用 |
| 作用范围 | 所有请求 | 单个 RouterFunction |
| 优先级控制 | Ordered | 调用顺序 |
| 适用场景 | 全局横切(日志、认证、CORS) | 局部业务过滤(权限、校验) |
| 与注解路由 | ✅ 支持 | ❌ 只支持函数式路由 |
eventd v2 的组合策略:
全局 WebFilter 链(按 Ordered 排序):
RequestIdFilter (HIGHEST_PRECEDENCE)
CorsWebFilter (HIGHEST_PRECEDENCE + 1,预检必须早于认证)
LoggingWebFilter (-100)
AuthPreCheckFilter (0)
↓
注解 Controller / 函数式 RouterFunction
↓
HandlerFilterFunction(仅函数式路由)
过滤器的响应式陷阱
不要在 WebFilter.filter() 中阻塞:
// 错误:阻塞过滤器会导致所有请求延迟
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 阻塞!
Thread.sleep(100); // ❌ 永远不要这样做
return chain.filter(exchange);
}
如果必须调用阻塞 API(如同步认证服务),包装为响应式:
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.fromCallable(() -> checkAuth(exchange))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(allowed -> Boolean.TRUE.equals(allowed)
? chain.filter(exchange) : reject(exchange));
}
与第 1.2 节一样:阻塞操作 + subscribeOn(boundedElastic) 是隔离阻塞代码的标准模式。
小结
| 概念 | 类型 | 控制方式 |
|---|---|---|
| 全局过滤 | WebFilter | @Component + Ordered |
| 局部过滤 | HandlerFilterFunction | .filter() 链式调用 |
| 优先级 | Ordered.getOrder() | 数字越小越早执行 |
| 短路 | 不调用 chain.filter() | 直接返回响应 |
| 跨线程状态 | exchange.getAttributes() | 响应式链安全 |
第 4 章结束。下一章进入请求响应处理:5.1 请求体读取与 JSON 解析 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。