4.3 WebFlux 过滤器

Spring WebFlux 过滤器体系详解:WebFilter 全局拦截链与 Ordered 优先级排序、HandlerFilterFunction 函数式路由局部过滤、过滤顺序与短路机制、跨线程状态传递以及网关日志/请求 ID/认证预检的实战配置。

两种过滤器

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 对比

维度WebFilterHandlerFilterFunction
注册方式@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 解析 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南