Spring WebFlux逻辑分支引发性能下降问题排查
解决Reactive WebFilter中Mono重复执行的问题
问题场景
我实现了如下AccountTokenFilter,用于验证请求令牌并将认证信息存入安全上下文:
@Component @RequiredArgsConstructor public class AccountTokenFilter implements WebFilter { private final TokenService tokenService; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { Mono<Authentication> authMono = tokenService.verifyToken(exchange.getRequest()) .map(AccountTokenAuthentication::new); return authMono.hasElement().flatMap(authenticated -> { if (authenticated) { return authMono.flatMap(auth -> chain.filter(exchange) .contextWrite(context -> ReactiveSecurityContextHolder.withAuthentication(auth))); } else { return chain.filter(exchange); } }); } }
过滤器链配置如下:
@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .csrf().disable() .cors().and() .httpBasic().disable() .formLogin().disable() .logout().disable() .authorizeExchange(spec -> spec .pathMatchers(HttpMethod.POST, "/accounts", "/tokens").permitAll() .pathMatchers(HttpMethod.GET, "/tokens/verify").permitAll() .anyExchange().authenticated() ) .addFilterAt(accountTokenFilter, SecurityWebFiltersOrder.AUTHENTICATION); return http.build(); }
目前遇到的问题是:tokenService.verifyToken()每次请求会被执行4次,预期仅执行一次。
问题原因
Reactor中的Mono是冷发布者,每一次订阅都会从头触发上游逻辑的执行。你的代码中,authMono被两次显式订阅:
- 调用
authMono.hasElement()时触发第一次订阅 - 进入
authenticated分支后调用authMono.flatMap()时触发第二次订阅
再加上Spring Security内部可能的隐式订阅,最终导致verifyToken()被多次执行。
修复方案
方案1:缓存Mono结果,避免重复执行
通过cache()操作符缓存authMono的结果,确保无论被订阅多少次,上游逻辑仅执行一次:
@Component @RequiredArgsConstructor public class AccountTokenFilter implements WebFilter { private final TokenService tokenService; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { Mono<Authentication> authMono = tokenService.verifyToken(exchange.getRequest()) .map(AccountTokenAuthentication::new) .cache(); // 缓存结果,仅执行一次上游逻辑 return authMono.hasElement().flatMap(authenticated -> { if (authenticated) { return authMono.flatMap(auth -> chain.filter(exchange) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth)) ); } else { return chain.filter(exchange); } }); } }
方案2:重构代码,仅订阅一次
更优雅的方式是调整逻辑,让整个流程只订阅一次verifyToken()的Mono,从根源避免重复执行:
@Component @RequiredArgsConstructor public class AccountTokenFilter implements WebFilter { private final TokenService tokenService; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return tokenService.verifyToken(exchange.getRequest()) .map(AccountTokenAuthentication::new) // 有认证信息时,写入上下文后继续过滤器链 .flatMap(auth -> chain.filter(exchange) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth))) // 无认证信息时,直接继续过滤器链 .switchIfEmpty(chain.filter(exchange)); } }
这种写法逻辑更简洁,且完全避免了多次订阅的问题,是推荐的解决方案。
额外排查点
如果修复后仍有重复执行的情况,可以检查:
- 是否有多个
AccountTokenFilter实例被注册到过滤器链中 tokenService.verifyToken()内部是否有导致重复执行的逻辑
内容的提问来源于stack exchange,提问作者Andrew Lalis
相关产品推荐
相关产品推荐

