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
相关产品推荐
相关产品推荐

