Spring Boot提交响应前处理:WebFlux迁移与RouterFunction适配问题
Great question! Since you're already using WebFilter but want options aligned with the functional style of RouterFunction, here are two tailored approaches to replicate your original interceptor logic:
1. HandlerFilterFunction (Router-Specific Filtering)
This is the most natural replacement for MVC interceptors when working with RouterFunction. It lets you wrap handler functions and apply cross-cutting logic directly within your router chain, rather than globally like WebFilter.
Here's a concrete implementation for your trace ID use case:
import org.springframework.web.reactive.function.server.HandlerFilterFunction; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import java.util.UUID; public class TraceIdFilterFunction implements HandlerFilterFunction<ServerResponse, ServerResponse> { @Override public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> next) { // Generate and set trace ID to ThreadLocal (note the caveat below!) String traceId = UUID.randomUUID().toString(); TraceIdHolder.setTraceId(traceId); // Proceed with the handler, then ensure we clean up the ThreadLocal regardless of outcome return next.handle(request) .doFinally(signalType -> TraceIdHolder.clearTraceId()); } // Helper class for ThreadLocal management private static class TraceIdHolder { private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>(); public static void setTraceId(String traceId) { TRACE_ID.set(traceId); } public static String getTraceId() { return TRACE_ID.get(); } public static void clearTraceId() { TRACE_ID.remove(); } } }
To apply this filter to your router chain, use the filter() method when building your routes:
@Configuration public class RouterConfig { @Bean public RouterFunction<ServerResponse> myRouter(MyHandler myHandler) { return RouterFunctions.route() .GET("/api/resource", myHandler::getResource) .POST("/api/resource", myHandler::createResource) // Apply the trace ID filter to all routes in this chain .filter(new TraceIdFilterFunction()) .build(); } }
2. Reactor Context (Recommended for Reactive Environments)
Quick heads-up: Using ThreadLocal in WebFlux carries risks! Since WebFlux uses a non-blocking, thread-reactive model, threads are reused across multiple requests. This can lead to ThreadLocal value leaks if cleanup isn't perfect.
The idiomatic, thread-safe way to handle request-scoped context in WebFlux is to use Reactor's Context instead. Here's how to adapt your logic:
First, update the filter to use Context instead of ThreadLocal:
public class TraceIdContextFilterFunction implements HandlerFilterFunction<ServerResponse, ServerResponse> { public static final String TRACE_ID_KEY = "traceId"; @Override public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> next) { String traceId = UUID.randomUUID().toString(); // Add trace ID to Reactor Context, which propagates through the reactive stream return next.handle(request) .contextWrite(context -> context.put(TRACE_ID_KEY, traceId)); } }
Then, access the trace ID from the Context in your handler function:
public Mono<ServerResponse> getResource(ServerRequest request) { return Mono.deferContextual(contextView -> { String traceId = contextView.get(TraceIdContextFilterFunction.TRACE_ID_KEY); // Use the trace ID in your business logic return ServerResponse.ok().bodyValue("Resource fetched with trace ID: " + traceId); }); }
This approach is safer because Reactor's Context is tied to the reactive stream, not the thread—no risk of leaks even with thread reuse.
Key Takeaways
HandlerFilterFunctionis perfect if you want to apply filtering to specific router chains (instead of all requests likeWebFilter).- Always prefer Reactor Context over
ThreadLocalin WebFlux to avoid thread-safety issues. - Both approaches ensure your trace ID is available during request processing and properly cleaned up after the response is handled.
内容的提问来源于stack exchange,提问作者Niranjan

