You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot WebFlux AOP切面如何适配Mono类型请求参数

现有实现的核心问题
  • 切点表达式用&& args(request)直接绑定方法入参,当入参改为Mono<TransactionInsightsData>时,切面拿到的request是未订阅的Mono冷流对象,不是实际反序列化后的请求实体,直接打日志、存审计记录拿到的都是Mono对象本身,拿不到真实请求参数。
  • 原逻辑末尾的.map(o -> result)是明显的逻辑错误:该操作会把流中实际的响应对象替换为原始Mono实例,会导致接口返回序列化异常、响应内容错误。
  • 在doOnSuccess/doOnError回调中手动调用.subscribe()触发审计日志保存,不符合响应式编程规范:手动启动的订阅脱离WebFlux管理的请求生命周期,会丢失Reactor上下文(比如链路追踪ID、请求自定义上下文),还可能出现请求已返回但日志写入未完成、上下文销毁导致空指针等问题。
  • 逻辑没有做参数类型兼容,只能处理普通实体入参,无法识别Mono包装的请求体。
改造后的实现代码
@Slf4j
@Aspect
@Order(3)
@Component
@RequiredArgsConstructor
public class AuditLogEventAspect {

    private final AuditEventManager auditEventManager;

    @Around("@annotation(auditLogEvent)")
    public Object logAround(ProceedingJoinPoint joinPoint, AuditLogEvent auditLogEvent) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Mono<Object> requestMono = Mono.empty();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Parameter[] parameters = signature.getMethod().getParameters();

        // 识别@RequestBody标注的参数,统一转换为Mono格式的真实请求流
        for (int i = 0; i < parameters.length; i++) {
            Parameter param = parameters[i];
            Object arg = args[i];
            if (arg == null) continue;
            if (param.isAnnotationPresent(RequestBody.class)) {
                if (arg instanceof Mono<?> monoArg) {
                    // Mono类型入参加cache,避免多次订阅导致请求体重复解析
                    requestMono = monoArg.map(req -> (Object) req).cache();
                    // 替换原参数为cache后的Mono,保证原方法能正常读取请求体
                    args[i] = requestMono;
                } else {
                    // 普通实体入参直接包装为Mono
                    requestMono = Mono.just(arg);
                }
                break;
            }
        }

        // 执行原方法获取响应流
        Mono<?> responseMono = (Mono<?>) joinPoint.proceed(args);
        Mono<Object> finalRequestMono = requestMono;

        return Mono.deferContextual(ctx -> {
            Map<String, String> contextMap = ctx.get(CONTEXT_MAP);
            return responseMono
                    // 处理正常响应场景
                    .flatMap(response -> finalRequestMono
                            .flatMap(request -> logSuccessExit(auditLogEvent, contextMap, request, response))
                            .doOnNext(auditEvent -> log.info("auditEvent {}", auditEvent))
                            // 日志写入不影响原响应返回
                            .thenReturn(response)
                    )
                    // 处理异常场景
                    .onErrorResume(e -> finalRequestMono
                            .flatMap(request -> logErrorExit(auditLogEvent, contextMap, request, (Exception) e))
                            .doOnNext(auditEvent -> log.info("auditEvent {}", auditEvent))
                            // 日志写入完成后继续抛出原异常,不破坏原异常处理逻辑
                            .then(Mono.error(e))
                    );
        });
    }

    private Mono<AuditEvent> logErrorExit(AuditLogEvent auditLogEvent, Map<String, String> contextMap, Object request, Exception error) {
        log.info("TRA request: {}", request);
        log.info("TRA error response: {}", error.getMessage());
        AuditEvent initialAuditEvent = auditEventManager.createAuditLogEvent(auditLogEvent, contextMap, request);
        return auditEventManager.saveExceptionEvent(initialAuditEvent, error);
    }

    private Mono<AuditEvent> logSuccessExit(AuditLogEvent auditLogEvent, Map<String, String> contextMap, Object request, Object response) {
        log.info("TRA request: {}", request);
        log.info("TRA response: {}", response);
        AuditEvent initialAuditEvent = auditEventManager.createAuditLogEvent(auditLogEvent, contextMap, request);
        return auditEventManager.saveSuccessEvent(initialAuditEvent, response);
    }
}
关键改造说明
  • 去掉了切点表达式中直接绑定入参的&& args(request)逻辑,改为通过方法签名识别@RequestBody标注的参数,同时兼容普通实体、Mono包装实体两种入参形式。
  • 对Mono类型的入参添加cache()处理,避免Mono被多次订阅导致请求体重复反序列化、流被消费后原方法无法读取参数的问题。
  • 删除了原代码中错误的.map(o -> result)逻辑,通过thenReturn(response)保证原接口响应值正常返回,不会破坏接口返回结构。
  • 移除了手动.subscribe()触发日志写入的逻辑,将审计日志保存逻辑通过flatMap串入主请求响应流,由WebFlux框架统一负责订阅调度,完全继承原流的Reactor上下文,不会出现链路信息丢失、生命周期不一致的问题。
  • 异常场景下通过then(Mono.error(e))保证原异常正常抛出,切面不会吞掉异常、改变原接口的错误处理逻辑。

注:如果后续需要支持Flux类型的流式请求体,只需要把对应Mono的处理逻辑替换为Flux即可,整体思路一致。

内容的提问来源于stack exchange,提问作者A A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:45:33