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

Spring Aspect搭配WebFlux的预期行为?基于@Around实现Prometheus指标遇问题

解决Reactor异步接口的Prometheus计时指标问题

嘿,我注意到你在尝试用@Around注解实现自定义的@TimeMetric来统计接口执行时间,但因为你的Controller返回的是Mono这种Reactor异步类型,直接用同步的计时逻辑肯定拿不到真实的执行耗时——这是异步代码里很常见的坑!

问题根源

你当前的代码里,joinPoint.proceed()会立刻返回一个Mono对象,但此时service.doSomeJob()的实际业务逻辑还没开始执行。如果只是在proceed()前后计时,得到的只是Mono对象初始化的时间,完全不是接口真正处理请求的耗时。

修正后的Aspect实现

我们需要针对Reactor类型的返回值,用它的生命周期钩子来捕获真正的完成时间。这里用doFinally(不管成功还是失败都会触发)来确保能统计到完整的执行时长:

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
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.Flux;
import reactor.core.publisher.Mono;

import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class TimeMetricAspect {

    private final Timer requestTimer;

    // 注入Micrometer的MeterRegistry,用来创建Timer指标
    public TimeMetricAspect(MeterRegistry meterRegistry) {
        this.requestTimer = Timer.builder("api_request_duration_ms")
                .description("API请求执行时长(毫秒)")
                .register(meterRegistry);
    }

    @Around("@annotation(TimeMetric)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();

        // 处理Mono类型的异步返回值
        if (result instanceof Mono) {
            return ((Mono<?>) result)
                    .doFinally(signalType -> {
                        long duration = System.currentTimeMillis() - startTime;
                        requestTimer.record(duration, TimeUnit.MILLISECONDS);
                    });
        }
        // 处理Flux类型的异步返回值
        else if (result instanceof Flux) {
            return ((Flux<?>) result)
                    .doFinally(signalType -> {
                        long duration = System.currentTimeMillis() - startTime;
                        requestTimer.record(duration, TimeUnit.MILLISECONDS);
                    });
        }
        // 处理同步方法的情况
        else {
            long duration = System.currentTimeMillis() - startTime;
            requestTimer.record(duration, TimeUnit.MILLISECONDS);
            return result;
        }
    }
}

关键细节说明

  • doFinally钩子:它会在Mono/Flux的整个数据流完成(包括成功、失败、取消)时触发,这样能准确统计从请求进入到业务逻辑完全结束的总耗时。
  • Prometheus指标选型:用Timer(底层对应Prometheus的Histogram)最合适,它不仅能记录耗时,还能生成分位数、平均值等统计数据,方便监控分析。
  • 兼容同步场景:代码里也处理了非Reactor类型的返回值,保证你的@TimeMetric注解能同时用于同步和异步接口。

额外小提示

如果你的项目是Spring Boot,其实可以直接用Micrometer自带的@Timed注解,它已经原生支持Reactor异步类型,不需要自己写AOP。但如果一定要自定义@TimeMetric注解,上面的代码就能完美解决你的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:10:48