Spring Framework中如何在ExchangeFilterFunction获取入站请求
在Spring WebClient过滤器中获取入站请求对象
问题概述
在Spring Framework API中实现了TokenFilter,其renewTokenFilter方法返回ExchangeFilterFunction,用于给WebClient出站请求注入OAuth2令牌。现在需要在该过滤器触发WebClient调用时,获取到之前的入站Web请求对象,但尝试通过自定义ReactiveRequestContextFilter传递上下文时,过滤器中获取的上下文为空。
现有代码
TokenFilter类
@Service public class TokenFilter { private final WebClient.Builder webClientBuilder; private final RequestCache requestCache; private static final Logger LOG = LoggerFactory.getLogger(TokenFilter.class); public TokenFilter(WebClient.Builder webClientBuilder, RequestCache requestCache) { this.webClientBuilder = webClientBuilder; this.requestCache = requestCache; } public ExchangeFilterFunction renewTokenFilter() { return (request, next) -> { LOG.info("outbound request path: {}", request.url().getPath()); // 此处需要获取入站请求对象 return processTokenFilter(request, next); }; } // 省略processTokenFilter方法实现 }
WebClient配置类
@Configuration public class WebClientFilterConfig { private static final Logger LOG = LoggerFactory.getLogger(WebClientFilterConfig.class); @Autowired private WebClient.Builder webClientBuilder; @Autowired private TokenFilter tokenFilter; @PostConstruct public void addFilterToWebClient() { LOG.info("configure the renewTokenFilter only once in this config"); webClientBuilder.filter(tokenFilter.renewTokenFilter()).build(); } }
业务调用类UserWebClient
public class UserWebClient { private static final Logger LOG = LoggerFactory.getLogger(UserWebClient.class); private final WebClient.Builder webClientBuilder; private final String userByAuthIdEp; public UserWebClient(WebClient.Builder webClientBuilder, String userByAuthIdEp) { this.webClientBuilder = webClientBuilder; this.userByAuthIdEp = userByAuthIdEp; } public Mono<UUID> getUserId(String authenticationId) { StringBuilder userByAuthId = new StringBuilder(userByAuthIdEp.replace("{authenticationId}", authenticationId)); LOG.info("make user call out to endpoint: {}", userByAuthId); WebClient.ResponseSpec responseSpec = webClientBuilder.build().get().uri(userByAuthId.toString()) .retrieve(); return responseSpec.bodyToMono(Map.class).map(map -> { LOG.info("user found: {}", map); return UUID.fromString(map.get("id").toString()); }); } }
无效的上下文传递尝试
自定义的ReactiveRequestContextFilter:
@Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.ANY) public class ReactiveRequestContextFilter implements WebFilter { private static final Logger LOG = LoggerFactory.getLogger(ReactiveRequestContextFilter.class); public static final String CONTEXT_KEY = "inboundRequest"; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); LOG.error("reactive request: {}", request.getPath()); return chain.filter(exchange).contextWrite(context -> context.put(CONTEXT_KEY, request)); } }
尝试在过滤器中获取上下文(结果为空):
public ExchangeFilterFunction headerFilter() { LOG.info("in headerFilter()"); return (request, next) -> ReactiveRequestContextHolder.getRequest().flatMap(r -> { // r为空,无法获取入站请求 return next.exchange(request); }); }
解决方案
问题出在上下文传递时机和WebClient构建方式上,以下是正确的实现步骤:
1. 正确传递Reactor上下文
修改自定义ReactiveRequestContextFilter,确保在处理请求前写入上下文,而非处理完成后:
@Configuration public class ReactiveRequestContextFilter implements WebFilter { private static final Logger LOG = LoggerFactory.getLogger(ReactiveRequestContextFilter.class); public static final String INBOUND_REQUEST_CONTEXT_KEY = "inboundRequest"; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest inboundRequest = exchange.getRequest(); LOG.info("Captured inbound request path: {}", inboundRequest.getPath()); // 在执行请求链前写入上下文,确保后续所有Mono/Flux都能获取到 return chain.filter(exchange) .contextWrite(Context.of(INBOUND_REQUEST_CONTEXT_KEY, inboundRequest)); } }
2. 在TokenFilter中正确获取上下文
不要使用ReactiveRequestContextHolder.getRequest()(它为Spring MVC设计,不适用于Reactive场景),直接通过Reactor上下文获取:
@Service public class TokenFilter { private static final Logger LOG = LoggerFactory.getLogger(TokenFilter.class); private final RequestCache requestCache; // 移除WebClient.Builder注入,避免循环依赖 public TokenFilter(RequestCache requestCache) { this.requestCache = requestCache; } public ExchangeFilterFunction renewTokenFilter() { return (request, next) -> { // 从Reactor上下文中获取入站请求 return Mono.deferContextual(contextView -> { ServerHttpRequest inboundRequest = contextView.get(ReactiveRequestContextFilter.INBOUND_REQUEST_CONTEXT_KEY); LOG.info("Got inbound request path: {}", inboundRequest.getPath()); // 示例:使用入站请求信息处理出站请求 String authHeader = inboundRequest.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); ClientRequest modifiedRequest = ClientRequest.from(request) .header(HttpHeaders.AUTHORIZATION, authHeader) // 可替换为OAuth2令牌逻辑 .build(); return next.exchange(modifiedRequest); }); }; } // 省略processTokenFilter方法 }
3. 修正WebClient构建方式
避免每次调用都重新构建WebClient,通过@Bean构建配置好的实例供其他类注入:
// 修改WebClientFilterConfig @Configuration public class WebClientFilterConfig { private static final Logger LOG = LoggerFactory.getLogger(WebClientFilterConfig.class); @Bean public WebClient webClient(WebClient.Builder webClientBuilder, TokenFilter tokenFilter) { LOG.info("Configuring renewTokenFilter for WebClient"); return webClientBuilder .filter(tokenFilter.renewTokenFilter()) .build(); } } // 修改UserWebClient,注入WebClient实例而非Builder public class UserWebClient { private static final Logger LOG = LoggerFactory.getLogger(UserWebClient.class); private final WebClient webClient; private final String userByAuthIdEp; public UserWebClient(WebClient webClient, String userByAuthIdEp) { this.webClient = webClient; this.userByAuthIdEp = userByAuthIdEp; } public Mono<UUID> getUserId(String authenticationId) { String userByAuthId = userByAuthIdEp.replace("{authenticationId}", authenticationId); LOG.info("Making user call to endpoint: {}", userByAuthId); return webClient.get() .uri(userByAuthId) .retrieve() .bodyToMono(Map.class) .map(map -> { LOG.info("User found: {}", map); return UUID.fromString(map.get("id").toString()); }); } }
关键说明
- Reactor上下文跟随Mono/Flux流传递,必须确保WebClient调用链路在入站请求的上下文范围内执行,不能脱离上下文构建新流。
- 不要在
PostConstruct中调用webClientBuilder.build(),这会提前固化WebClient,导致后续配置修改不生效。 ReactiveRequestContextHolder主要用于Spring MVC场景,Reactive环境下使用Mono.deferContextual获取上下文更可靠。
内容的提问来源于stack exchange,提问作者Katlock
相关产品推荐
相关产品推荐

