Spring WebFlux如何在Controller方法调用后添加自定义后置逻辑
问题原因分析
首先你选择WebFilter作为实现方式是可行的,但现有写法存在两个核心错误导致不生效:
- 执行时机完全错误:
chain.filter(exchange)返回的Mono<Void>代表的是整个请求处理全流程的异步结果,包含Controller调用、响应编码、响应发送到客户端的所有步骤。当这个Mono触发doOnNext/map/doFinally回调时,响应已经完全提交给客户端了,此时修改状态、加延迟都不会对已经发出去的响应产生任何影响。 - 阻塞操作违反响应式规范:你现有的
Mode实现用了Thread.sleep,会直接阻塞WebFlux底层的Netty IO线程,导致服务吞吐量暴跌,必须改为非阻塞的响应式实现。
实现方案
首先把Mode接口改造为响应式风格,非阻塞、可拼接进原有响应式流程:
public interface Mode { <T> Mono<T> apply(Mono<T> originalResult); }
各个Mode实现改造如下:
失败模式
import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Mono; public class FailMode implements Mode { @Override public <T> Mono<T> apply(Mono<T> originalResult) { // 原始业务逻辑执行成功后,返回403错误 return originalResult.flatMap(res -> Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "Fail mode enabled"))); } }
延迟返回模式(长响应模式)
import reactor.core.publisher.Mono; import java.time.Duration; public class WaitMode implements Mode { private final Duration waitDuration; public WaitMode(long waitMs) { this.waitDuration = Duration.ofMillis(waitMs); } @Override public <T> Mono<T> apply(Mono<T> originalResult) { // 原始逻辑执行成功后,延迟指定时间再返回结果 return originalResult.delayElement(waitDuration); } }
超时模式
import reactor.core.publisher.Mono; import java.time.Duration; public class TimeoutMode implements Mode { private final Duration timeout; public TimeoutMode(long timeoutMs) { this.timeout = Duration.ofMillis(timeoutMs); } @Override public <T> Mono<T> apply(Mono<T> originalResult) { // 原始逻辑执行成功后,等待直到超时,也可以根据需求调整为超时直接返回错误 return originalResult.delayElement(timeout); } }
逻辑织入方案(二选一即可)
方案1:AOP切Controller方法(最简洁,推荐)
通过切面拦截所有Controller方法的返回值,把Mode逻辑拼接进原有的响应式流程:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Aspect @Component public class ModeAspect { // 可通过配置动态切换当前激活的Mode private final Mode activeMode = new FailMode(); @Around("execution(* 你的Controller包路径.*.*(..))") public Object wrapControllerResult(ProceedingJoinPoint pjp) throws Throwable { Object result = pjp.proceed(); if (result instanceof Mono<?> monoResult) { return activeMode.apply(monoResult); } // 如果有Flux类型返回值也可自行扩展处理 return result; } }
方案2:WebFilter实现(适合全局拦截所有请求,包括静态资源等)
如果需要用WebFilter实现,需要调整逻辑执行时机,在响应提交前对返回结果做处理,需要自定义ServerHttpResponse包装类拦截writeWith方法,实现复杂度远高于AOP方案,非必要场景优先选择AOP。
内容的提问来源于stack exchange,提问作者Volodya Lombrozo
相关产品推荐
相关产品推荐

