01-如何监听接口调用情况?

01-如何监听接口调用情况? 🔍

本文档介绍在 SpringBoot 项目中监听每个接口的调用次数、传入参数、返回参数、响应耗时的多种实现方案

章节阅读路线图 🗺️

  1. AOP 切面方案(最推荐) → 使用 Spring AOP 无侵入式监控所有接口
  2. 自定义注解方案 → 选择性监控,只针对关键接口
  3. HandlerInterceptor 拦截器方案 → 基于 Spring MVC 拦截器实现
  4. Micrometer + Actuator 生产级方案 → 接入 Prometheus + Grafana 可视化监控
  5. 方案对比与总结 → 根据场景选择最合适的方案

1. AOP 切面方案(最推荐) 🎯

使用 Spring AOP 无侵入式监控所有或者指定接口,记录调用次数、参数、返回值和耗时

AOP(Aspect Oriented Programming)是 Spring 框架的核心特性之一,它可以在不修改原有代码的情况下,对方法进行增强。利用 AOP,我们只需要编写一个切面类,就可以统一拦截所有 Controller 方法,实现接口监控。

1.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.x</version>
</dependency>

💡 spring-boot-starter-aop 已包含 AspectJ 依赖。fastjson 用于将参数和返回值序列化为 JSON 字符串,也可以用 Jackson(SpringBoot 默认自带)。

1.2 编写切面类

java 复制代码
package com.example.aspect;                                  // 包名根据实际项目调整

import com.alibaba.fastjson.JSON;                            // 引入 JSON 序列化工具,用于格式化参数和返回值
import lombok.extern.slf4j.Slf4j;                            // 引入 Slf4j 注解,自动生成 log 日志对象
import org.aspectj.lang.ProceedingJoinPoint;                 // 引入 ProceedingJoinPoint,用于执行目标方法并获取上下文
import org.aspectj.lang.annotation.Around;                   // 引入环绕通知注解,@Around 可以包裹目标方法的执行
import org.aspectj.lang.annotation.Aspect;                   // 引入切面注解,标识该类为 AOP 切面
import org.aspectj.lang.annotation.Pointcut;                 // 引入切点注解,定义拦截规则
import org.springframework.stereotype.Component;              // 引入 Component 注解,将切面注入 Spring 容器
import org.springframework.web.context.request.RequestContextHolder;        // 引入 RequestContextHolder,获取当前请求的上下文
import org.springframework.web.context.request.ServletRequestAttributes;    // 引入 ServletRequestAttributes,获取 HttpServletRequest

import javax.servlet.http.HttpServletRequest;                // 引入 HttpServletRequest,用于获取请求 URL、方法类型等
import java.util.Arrays;                                     // 引入 Arrays 工具类,用于数组操作
import java.util.Map;                                        // 引入 Map 接口,用于存储调用次数统计
import java.util.concurrent.ConcurrentHashMap;               // 引入 ConcurrentHashMap,线程安全的计数器容器
import java.util.concurrent.atomic.AtomicLong;               // 引入 AtomicLong,线程安全的原子计数器

/**
 * 接口监控切面
 *
 * 功能:统计每个接口的调用次数、记录传入参数、返回参数、响应耗时
 *
 * Args:
 *     无
 *
 * Returns:
 *     无(通过 log 输出监控信息)
 *
 * Example:
 *     自动生效,无需手动调用
 */
@Slf4j                                      // Lombok 注解,自动生成 log 字段,示例:log.info("消息")
@Aspect                                     // 标识当前类为切面类
@Component                                  // 将当前类注入 Spring 容器
public class ApiMonitorAspect {

    // 线程安全的调用次数统计容器
    // Key: 接口方法名(类名+方法名),Value: 原子计数器
    // 示例:{"UserController.getUser" → 42, "OrderController.createOrder" → 15}
    private final Map<String, AtomicLong> callCountMap = new ConcurrentHashMap<>();

    /**
     * 定义切点:拦截 com.example.controller 包下所有类的所有方法
     *
     * Args:
     *     无
     *
     * Returns:
     *     无
     *
     * Example:
     *     execution(* com.example.controller.*.*(..)) 表示拦截 controller 包下所有方法
     */
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerPointcut() {
        // 切点方法,无实际逻辑,仅用于定义拦截规则
    }

    /**
     * 环绕通知:在目标方法执行前后插入监控逻辑
     *
     * Args:
     *     joinPoint: 连接点对象,包含目标方法的全部上下文信息
     *
     * Returns:
     *     Object: 目标方法的返回值,原封不动返回
     *
     * Example:
     *     @Around("controllerPointcut()")
     *     public Object monitorApi(ProceedingJoinPoint joinPoint)
     */
    @Around("controllerPointcut()")
    public Object monitorApi(ProceedingJoinPoint joinPoint) throws Throwable {
        // ===== 1. 获取请求上下文信息 =====
        // 通过 RequestContextHolder 获取当前线程绑定的 Servlet 请求属性
        // 数据流动:HttpServletRequest → RequestContextHolder → 提取 URL/Method/IP
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String requestUrl = request.getRequestURL().toString();    // 获取完整请求 URL,示例:"http://localhost:8080/api/user/list"
        String httpMethod = request.getMethod();                   // 获取 HTTP 方法类型,示例:"GET"、"POST"
        String clientIp = request.getRemoteAddr();                 // 获取客户端 IP 地址,示例:"127.0.0.1"

        // ===== 2. 获取方法信息 =====
        // 获取目标方法的全限定名(包名+类名+方法名)
        // 数据流动:joinPoint.getSignature() → 方法签名 → 类名.方法名
        String className = joinPoint.getTarget().getClass().getSimpleName();           // 获取类名,示例:"UserController"
        String methodName = className + "." + joinPoint.getSignature().getName();     // 组装方法名,示例:"UserController.getUser"

        // ===== 3. 统计调用次数 =====
        // 从 callCountMap 中获取或创建该方法的原子计数器,然后自增
        // 数据流动:methodName → ConcurrentHashMap.get() → AtomicLong.incrementAndGet() → 返回递增后的值
        long currentCount = callCountMap.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();

        // ===== 4. 获取传入参数 =====
        // 获取目标方法的所有参数值列表
        // 数据流动:joinPoint.getArgs() → Object[] → JSON.toJSONString() → JSON 字符串
        Object[] args = joinPoint.getArgs();
        String requestParams = JSON.toJSONString(args);           // 将参数数组序列化为 JSON,示例:"[{"id":1,"name":"张三"}]"

        // ===== 5. 记录开始时间 =====
        // 记录当前时间戳,用于后续计算接口耗时
        long startTime = System.currentTimeMillis();

        // ===== 6. 执行目标方法 =====
        // proceed() 会执行被拦截的原始方法,返回原始结果
        // 数据流动:joinPoint.proceed() → 目标方法执行 → 返回 Object 类型结果
        Object result = joinPoint.proceed();

        // ===== 7. 计算响应耗时 =====
        // 用结束时间减去开始时间,得到接口处理耗时
        long elapsed = System.currentTimeMillis() - startTime;    // 计算耗时,单位:毫秒

        // ===== 8. 获取返回参数 =====
        // 将方法返回值序列化为 JSON 字符串
        // 数据流动:result Object → JSON.toJSONString() → JSON 字符串
        String responseResult = JSON.toJSONString(result);

        // ===== 9. 输出监控日志 =====
        // 汇总所有监控信息,统一输出
        log.info("\n========================================\n" +
                 "接口监控信息:\n" +
                 "  请求地址: {} {}\n" +
                 "  客户端IP: {}\n" +
                 "  接口方法: {}\n" +
                 "  调用次数(累计): {}\n" +
                 "  传入参数: {}\n" +
                 "  返回参数: {}\n" +
                 "  响应耗时: {}ms\n" +
                 "========================================",
                httpMethod, requestUrl,                          // 填充请求地址
                clientIp,                                        // 填充客户端 IP
                methodName,                                      // 填充接口方法名
                currentCount,                                    // 填充累计调用次数
                requestParams.length() > 500 ? requestParams.substring(0, 500) + "..." : requestParams,  // 参数过长时截断
                responseResult.length() > 500 ? responseResult.substring(0, 500) + "..." : responseResult,  // 返回值过长时截断
                elapsed);                                        // 填充响应耗时

        // 原封不动返回目标方法的执行结果,不对业务逻辑产生任何影响
        return result;
    }

    /**
     * 获取指定接口的累计调用次数
     *
     * Args:
     *     methodName: 方法名,格式为 "类名.方法名",示例:"UserController.getUser"
     *
     * Returns:
     *     long: 该接口的累计调用次数,如果未找到返回 0
     *
     * Example:
     *     long count = apiMonitorAspect.getCallCount("UserController.getUser");
     */
    public long getCallCount(String methodName) {
        AtomicLong counter = callCountMap.get(methodName);       // 从 Map 中查询指定方法的计数器
        return counter != null ? counter.get() : 0;              // 如果存在则返回计数,否则返回 0
    }

    /**
     * 获取所有接口的调用次数统计(可用于对外暴露 API)
     *
     * Args:
     *     无
     *
     * Returns:
     *     Map<String, Long>: 所有接口的调用次数映射表,示例:{"UserController.getUser": 42}
     *
     * Example:
     *     Map<String, Long> allStats = apiMonitorAspect.getAllCallCounts();
     */
    public Map<String, Long> getAllCallCounts() {
        Map<String, Long> result = new ConcurrentHashMap<>();    // 创建结果容器
        callCountMap.forEach((key, value) -> result.put(key, value.get()));  // 遍历并转换 AtomicLong 为普通 Long
        return result;                                           // 返回不可修改的统计视图
    }
}

1.3 输出示例

复制代码
2026-05-28 14:30:22.156 [http-nio-8080-exec-1] INFO  c.e.aspect.ApiMonitorAspect -
========================================
接口监控信息:
  请求地址: GET http://localhost:8080/api/user/list
  客户端IP: 127.0.0.1
  接口方法: UserController.getUserList
  调用次数(累计): 1
  传入参数: [{"page":1,"size":10}]
  返回参数: {"code":200,"data":[{"id":1,"name":"张三"}],"msg":"成功"}
  响应耗时: 45ms
========================================

2026-05-28 14:30:25.891 [http-nio-8080-exec-2] INFO  c.e.aspect.ApiMonitorAspect -
========================================
接口监控信息:
  请求地址: POST http://localhost:8080/api/user/create
  客户端IP: 192.168.1.100
  接口方法: UserController.createUser
  调用次数(累计): 1
  传入参数: [{"name":"李四","age":25,"email":"lisi@example.com"}]
  返回参数: {"code":200,"data":{"id":2},"msg":"创建成功"}
  响应耗时: 120ms
========================================

从输出可以看到,监控信息完整覆盖了需求中的四个维度:

  • 调用次数调用次数(累计): 1
  • 传入参数传入参数: [{"page":1,"size":10}]
  • 返回参数返回参数: {"code":200,"data":[...]}
  • 响应耗时响应耗时: 45ms

参考资料:


2. 自定义注解方案(选择性监控) 🏷️

通过自定义注解实现选择性监控,只对关键接口生效

如果不想监控所有接口,可以自定义一个注解,只有在方法上添加了该注解的接口才进行监控。

2.1 定义注解

java 复制代码
package com.example.annotation;                               // 包名根据实际项目调整

import java.lang.annotation.ElementType;                      // 引入 ElementType 枚举,用于指定注解的适用范围
import java.lang.annotation.Retention;                        // 引入 Retention 枚举,用于指定注解的生命周期
import java.lang.annotation.RetentionPolicy;                  // 引入 RetentionPolicy,RetentionPolicy.RUNTIME 表示运行时保留
import java.lang.annotation.Target;                           // 引入 Target 注解,用于限制注解能标注的位置

/**
 * 接口监控注解
 *
 * 用于标记需要监控调用次数、参数、耗时等信息的接口方法
 *
 * Args:
 *     value: 接口描述信息,可选,示例:"用户登录接口"
 *
 * Returns:
 *     无(注解本身不返回值)
 *
 * Example:
 *     @Monitored("用户登录接口")
 *     public Result login(@RequestBody LoginDTO dto)
 */
@Target(ElementType.METHOD)                  // 注解只能标注在方法上
@Retention(RetentionPolicy.RUNTIME)          // 注解在运行时保留,AOP 才能在运行时读取
public @interface Monitored {

    /**
     * 接口描述信息
     *
     * Args:
     *     无
     *
     * Returns:
     *     String: 接口的描述说明,默认为空字符串
     *
     * Example:
     *     @Monitored("用户登录接口")
     */
    String value() default "";               // 可选属性,用于描述接口用途
}

2.2 修改切面类

java 复制代码
/**
 * 基于自定义注解的接口监控切面
 *
 * 功能:只对标注了 @Monitored 注解的方法进行监控
 *
 * Args:
 *     无
 *
 * Returns:
 *     无(通过 log 输出监控信息)
 *
 * Example:
 *     在 Controller 方法上添加 @Monitored 即可生效
 */
@Slf4j
@Aspect
@Component
public class MonitoredApiAspect {

    // 线程安全的调用次数统计容器
    // Key: 接口方法名,Value: 原子计数器
    private final Map<String, AtomicLong> callCountMap = new ConcurrentHashMap<>();

    /**
     * 定义切点:拦截所有标注了 @Monitored 注解的方法
     *
     * Args:
     *     monitored: 注入的注解实例,可在通知中读取注解属性
     *
     * Returns:
     *     无
     *
     * Example:
     *     @annotation(com.example.annotation.Monitored) 匹配所有标有 @Monitored 的方法
     */
    @Pointcut("@annotation(com.example.annotation.Monitored)")
    public void monitoredPointcut() {
        // 切点方法,无实际逻辑
    }

    /**
     * 环绕通知:拦截并监控标注了 @Monitored 的方法
     *
     * Args:
     *     joinPoint: 连接点对象,包含目标方法的全部上下文信息
     *     monitored: 目标方法上的 @Monitored 注解实例,可读取 value 属性
     *
     * Returns:
     *     Object: 目标方法的返回值
     *
     * Example:
     *     @Around("monitoredPointcut() && @annotation(monitored)")
     */
    @Around("monitoredPointcut() && @annotation(monitored)")
    public Object monitorAnnotatedApi(ProceedingJoinPoint joinPoint, Monitored monitored) throws Throwable {
        // 获取注解中填写的接口描述,示例:"用户登录接口"
        String description = monitored.value();

        // 获取方法信息
        String methodName = joinPoint.getTarget().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();

        // 统计调用次数
        long currentCount = callCountMap.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();

        // 获取传入参数
        Object[] args = joinPoint.getArgs();
        String requestParams = JSON.toJSONString(args);

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 记录开始时间
        long startTime = System.currentTimeMillis();

        // 执行目标方法
        Object result = joinPoint.proceed();

        // 计算耗时
        long elapsed = System.currentTimeMillis() - startTime;

        // 输出监控日志
        log.info("[接口监控] {} | {} | 方法: {} | 累计调用: {} | 参数: {} | 耗时: {}ms | 返回: {}",
                request.getMethod(), request.getRequestURL(),
                methodName, currentCount,
                requestParams, elapsed,
                JSON.toJSONString(result));

        return result;
    }
}

2.3 使用方式

java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {

    @Monitored("用户登录接口")              // 标注需要监控的方法
    @PostMapping("/login")
    public Result login(@RequestBody LoginDTO loginDTO) {
        // 业务逻辑
        return Result.success("登录成功");
    }

    @GetMapping("/list")                    // 未标注 @Monitored,不会被监控
    public Result list() {
        return Result.success(userService.list());
    }
}

参考资料:


3. HandlerInterceptor 拦截器方案 🚦

使用 Spring MVC 的 HandlerInterceptor,在请求处理前后插入监控逻辑

Interceptor 是 Spring MVC 特有的拦截机制,在请求进入 Controller 之前和响应返回之后执行。适合只需要监控 HTTP 请求的场景。

3.1 实现拦截器

java 复制代码
package com.example.interceptor;                             // 包名根据实际项目调整

import com.alibaba.fastjson.JSON;                            // 引入 JSON 序列化工具,用于格式化请求参数
import lombok.extern.slf4j.Slf4j;                            // 引入 Slf4j 注解
import org.springframework.stereotype.Component;              // 引入 Component 注解,将拦截器注入 Spring 容器
import org.springframework.web.servlet.HandlerInterceptor;   // 引入 HandlerInterceptor 接口,需要实现 preHandle 和 afterCompletion
import org.springframework.web.servlet.ModelAndView;         // 引入 ModelAndView 类

import javax.servlet.http.HttpServletRequest;               // 引入 HttpServletRequest
import javax.servlet.http.HttpServletResponse;               // 引入 HttpServletResponse
import java.util.Map;                                        // 引入 Map 接口,用于存储统计信息
import java.util.concurrent.ConcurrentHashMap;               // 引入 ConcurrentHashMap,线程安全的统计容器
import java.util.concurrent.atomic.AtomicLong;               // 引入 AtomicLong,线程安全的计数器

/**
 * 接口监控拦截器
 *
 * 功能:在请求处理前后记录调用次数、参数、耗时等监控信息
 *
 * Args:
 *     无
 *
 * Returns:
 *     无(通过 log 输出监控信息)
 *
 * Example:
 *     在 WebConfig 中注册该拦截器即可生效
 */
@Slf4j
@Component
public class ApiMonitorInterceptor implements HandlerInterceptor {

    // 线程安全的统计容器
    // Key: URL(请求路径),Value: 原子计数器
    // 示例:{"/api/user/list": 42, "/api/user/create": 15}
    private final Map<String, AtomicLong> urlCountMap = new ConcurrentHashMap<>();

    // 使用 ThreadLocal 存储每个请求的开始时间,确保线程隔离
    // 数据流动:preHandle 中 set(startTime) → afterCompletion 中 get() 计算耗时 → finally 中 remove() 清理
    private final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();

    // 使用 ThreadLocal 存储每个请求的 URL,用于在 afterCompletion 中统计
    private final ThreadLocal<String> urlHolder = new ThreadLocal<>();

    /**
     * 请求处理前执行
     *
     * Args:
     *     request: HTTP 请求对象
     *     response: HTTP 响应对象
     *     handler: 处理该请求的处理器(Controller 方法)
     *
     * Returns:
     *     boolean: true 表示继续执行,false 表示中断请求
     *
     * Example:
     *     preHandle 中记录开始时间和请求参数
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取请求 URL,示例:"/api/user/list"
        String requestUrl = request.getRequestURI();

        // 将请求 URL 存入 ThreadLocal,供 afterCompletion 使用
        urlHolder.set(requestUrl);

        // 该请求的调用次数加 1
        // 数据流动:urlCountMap.get(requestUrl) → AtomicLong.incrementAndGet() → 返回自增后的值
        long currentCount = urlCountMap.computeIfAbsent(requestUrl, k -> new AtomicLong(0)).incrementAndGet();

        // 记录请求开始时间,存入 ThreadLocal
        startTimeHolder.set(System.currentTimeMillis());

        // 记录请求信息
        log.info("[请求开始] {} {} | 累计调用: {} | 参数: {}",
                request.getMethod(),                         // HTTP 方法,示例:"GET"
                requestUrl,                                  // 请求路径,示例:"/api/user/list"
                currentCount,                                // 累计调用次数
                JSON.toJSONString(request.getParameterMap()) // 请求参数 Map,示例:{"page":["1"],"size":["10"]}
        );

        return true;  // 返回 true,继续执行后续处理
    }

    /**
     * 请求完成后执行(无论是否异常)
     *
     * Args:
     *     request: HTTP 请求对象
     *     response: HTTP 响应对象
     *     handler: 处理该请求的处理器
     *     ex: 处理过程中抛出的异常(如果有)
     *
     * Returns:
     *     无
     *
     * Example:
     *     afterCompletion 中计算耗时并输出日志
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            // 从 ThreadLocal 中取出开始时间
            Long startTime = startTimeHolder.get();
            if (startTime == null) {                          // 安全校验:如果 startTime 为空,说明 preHandle 未执行成功
                return;                                       // 直接返回,不进行后续处理
            }

            // 计算处理耗时,单位:毫秒
            // 数据流动:结束时间戳 - 开始时间戳 → 耗时毫秒数
            long elapsed = System.currentTimeMillis() - startTime;

            // 获取请求 URL
            String requestUrl = urlHolder.get();

            // 获取 HTTP 状态码,示例:200、404、500
            int status = response.getStatus();

            // 输出监控日志
            log.info("[请求结束] {} | 状态: {} | 耗时: {}ms{}",
                    requestUrl,                              // 请求路径
                    status,                                  // HTTP 状态码
                    elapsed,                                 // 响应耗时
                    ex != null ? " | 异常: " + ex.getMessage() : ""  // 如果有异常则追加异常信息
            );
        } finally {
            // 清理 ThreadLocal,防止内存泄漏(线程池中的线程会被复用)
            startTimeHolder.remove();
            urlHolder.remove();
        }
    }
}

3.2 注册拦截器

java 复制代码
package com.example.config;                                  // 包名根据实际项目调整

import com.example.interceptor.ApiMonitorInterceptor;         // 引入自定义拦截器
import org.springframework.beans.factory.annotation.Autowired;  // 引入自动注入注解
import org.springframework.context.annotation.Configuration;   // 引入 Configuration 注解,标识为配置类
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  // 引入拦截器注册器
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;     // 引入 WebMvcConfigurer 接口,用于自定义 Spring MVC 配置

/**
 * Web MVC 配置类
 *
 * 功能:注册自定义拦截器到 Spring MVC 拦截器链中
 *
 * Args:
 *     无
 *
 * Returns:
 *     无
 *
 * Example:
 *     @Configuration 注解的类会自动被 Spring 扫描并加载配置
 */
@Configuration                                                              // 标识为配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private ApiMonitorInterceptor apiMonitorInterceptor;                     // 注入自定义的接口监控拦截器

    /**
     * 添加拦截器到 Spring MVC 框架
     *
     * Args:
     *     registry: 拦截器注册器,用于添加和配置拦截器
     *
     * Returns:
     *     无
     *
     * Example:
     *     registry.addInterceptor(interceptor).addPathPatterns("/**");
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiMonitorInterceptor)                       // 添加自定义拦截器
                .addPathPatterns("/**");                                     // 拦截所有请求路径
    }
}

参考资料:


4. Micrometer + Actuator 生产级方案 📊

使用 Micrometer 指标框架 + Spring Actuator 暴露指标,接入 Prometheus + Grafana 实现可视化监控

前面的方案适合开发调试和简单的日志监控,但在生产环境中,我们需要持久化的指标存储和可视化看板。Micrometer 是 Spring Boot 官方推荐的指标采集框架,可以无缝对接 Prometheus、InfluxDB 等监控系统。

4.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

4.2 配置 application.yml

yaml 复制代码
spring:
  application:
    name: my-api-service                                          # 应用名称,作为 Prometheus 指标的标签

management:
  endpoints:
    web:
      exposure:
        include: prometheus,metrics,health                        # 暴露 Prometheus、Metrics、Health 端点
  metrics:
    tags:
      application: ${spring.application.name}                     # 为所有指标添加 application 标签
    distribution:
      percentiles-histogram:
        http.server.requests: true                                # 为 HTTP 请求指标启用百分位数直方图
      slo:
        http.server.requests: 10ms, 50ms, 100ms, 200ms, 500ms, 1s  # 定义服务级别目标(SLO)的耗时边界

4.3 验证指标端点

启动应用后,访问 http://localhost:8080/actuator/prometheus,可以看到类似以下输出:

复制代码
# HELP http_server_requests_seconds
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{application="my-api-service",method="GET",outcome="SUCCESS",status="200",uri="/api/user/list"} 15.0
http_server_requests_seconds_sum{application="my-api-service",method="GET",outcome="SUCCESS",status="200",uri="/api/user/list"} 0.892
http_server_requests_seconds_max{application="my-api-service",method="GET",outcome="SUCCESS",status="200",uri="/api/user/list"} 0.124

指标含义:

指标 含义
http_server_requests_seconds_count 接口调用总次数
http_server_requests_seconds_sum 接口调用累计耗时(秒)
http_server_requests_seconds_max 接口调用最大耗时(秒)

4.4 自定义业务指标

除了框架自动采集的 HTTP 指标,还可以自定义业务指标,例如按接口记录详细参数:

java 复制代码
package com.example.metrics;                                   // 包名根据实际项目调整

import io.micrometer.core.instrument.Counter;                  // 引入 Micrometer 计数器,用于统计调用次数
import io.micrometer.core.instrument.MeterRegistry;            // 引入 MeterRegistry,Micrometer 指标注册中心
import io.micrometer.core.instrument.Timer;                    // 引入 Micrometer 计时器,用于统计耗时
import lombok.extern.slf4j.Slf4j;                              // 引入 Slf4j 注解
import org.aspectj.lang.ProceedingJoinPoint;                   // 引入 ProceedingJoinPoint
import org.aspectj.lang.annotation.Around;                     // 引入环绕通知注解
import org.aspectj.lang.annotation.Aspect;                     // 引入切面注解
import org.aspectj.lang.annotation.Pointcut;                   // 引入切点注解
import org.springframework.stereotype.Component;                // 引入 Component 注解

import java.util.concurrent.TimeUnit;                          // 引入 TimeUnit,用于时间单位转换

/**
 * Micrometer 自定义指标切面
 *
 * 功能:统计每个接口的调用次数和耗时,以 Prometheus 指标格式暴露
 *
 * Args:
 *     无
 *
 * Returns:
 *     无(指标数据自动注册到 MeterRegistry)
 *
 * Example:
 *     结合 Actuator + Prometheus + Grafana 实现可视化监控
 */
@Slf4j
@Aspect
@Component
public class MicrometerMetricsAspect {

    private final MeterRegistry meterRegistry;                  // Micrometer 指标注册中心,用于创建和管理指标

    /**
     * 构造方法注入 MeterRegistry
     *
     * Args:
     *     meterRegistry: Spring Boot 自动配置的 MeterRegistry 实例
     *
     * Returns:
     *     无
     */
    public MicrometerMetricsAspect(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    /**
     * 定义切点:拦截所有 Controller 方法
     */
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerPointcut() {}

    /**
     * 环绕通知:采集接口调用次数和耗时指标
     *
     * Args:
     *     joinPoint: 连接点对象
     *
     * Returns:
     *     Object: 目标方法的返回值
     */
    @Around("controllerPointcut()")
    public Object recordMetrics(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取接口名称,用于指标标签
        String methodName = joinPoint.getTarget().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();

        // 创建计时器:统计该接口的耗时分布
        // 数据流动:Timer.Sample 记录开始 → 方法执行 → sample.stop() 记录结束 → 指标注册到 Prometheus
        Timer.Sample sample = Timer.start(meterRegistry);

        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            return result;
        } finally {
            // 停止计时器,并将指标注册到 MeterRegistry
            // 数据流动:sample.stop(timer) → /actuator/prometheus 暴露 → Prometheus 采集 → Grafana 展示
            sample.stop(Timer.builder("api.call.duration")           // 指标名称
                    .description("接口调用耗时")                       // 指标描述
                    .tag("method", methodName)                        // 标签:接口方法名
                    .tag("class", joinPoint.getTarget().getClass().getSimpleName())  // 标签:类名
                    .register(meterRegistry));                        // 注册到 MeterRegistry

            // 增加调用次数计数器
            // 数据流动:Counter.increment() → 指标值 +1 → Prometheus 采集
            Counter counter = Counter.builder("api.call.count")      // 指标名称
                    .description("接口调用次数")                        // 指标描述
                    .tag("method", methodName)                        // 标签:接口方法名
                    .register(meterRegistry);                        // 注册到 MeterRegistry
            counter.increment();                                      // 计数器加 1
        }
    }
}

配置完成后,/actuator/prometheus 端点会额外输出自定义指标:

复制代码
# HELP api_call_duration_seconds 接口调用耗时
# TYPE api_call_duration_seconds summary
api_call_duration_seconds_count{method="UserController.getUserList",} 5.0
api_call_duration_seconds_sum{method="UserController.getUserList",} 0.345

# HELP api_call_count_total 接口调用次数
# TYPE api_call_count_total counter
api_call_count_total{method="UserController.getUserList",} 5.0

参考资料:


5. 基于事件监听方案 📡

使用 Spring 框架的 ServletRequestHandledEvent 事件监听 HTTP 请求处理完成事件

Spring 框架在每次 HTTP 请求处理完成后会发布 ServletRequestHandledEvent 事件,包含了请求的详细信息。只需监听该事件即可获取接口耗时,无需任何切面或拦截器配置。

注意 :此方案只能获取耗时和基本请求信息(URL、方法、状态码),无法获取传入参数和返回参数

java 复制代码
package com.example.listener;                                  // 包名根据实际项目调整

import lombok.extern.slf4j.Slf4j;                              // 引入 Slf4j 注解
import org.springframework.context.ApplicationListener;         // 引入 ApplicationListener 接口
import org.springframework.stereotype.Component;                // 引入 Component 注解
import org.springframework.web.context.support.ServletRequestHandledEvent;  // 引入请求处理事件类

/**
 * 请求耗时事件监听器
 *
 * 功能:监听 ServletRequestHandledEvent 事件,记录每个接口的调用耗时
 *       注意:此方案无法获取请求参数和返回参数,仅能获取 URL、方法、耗时
 *
 * Args:
 *     无
 *
 * Returns:
 *     无(通过 log 输出监控信息)
 *
 * Example:
 *     自动生效,无需额外配置
 */
@Slf4j
@Component
public class RequestTimeEventListener implements ApplicationListener<ServletRequestHandledEvent> {

    /**
     * 事件处理方法:ServletRequestHandledEvent 发布时自动调用
     *
     * Args:
     *     event: ServletRequestHandledEvent 事件对象,包含请求的 URL、方法、耗时、客户端地址等信息
     *
     * Returns:
     *     无
     *
     * Example:
     *     请求完成后自动触发,输出:
     *     "clientAddress=127.0.0.1, requestUrl=/api/user/list, method=GET, costTime=45ms"
     */
    @Override
    public void onApplicationEvent(ServletRequestHandledEvent event) {
        // 获取客户端 IP 地址,示例:"127.0.0.1"
        String clientAddress = event.getClientAddress();

        // 获取请求 URL,示例:"/api/user/list"
        String requestUrl = event.getRequestUrl();

        // 获取 HTTP 方法类型,示例:"GET"、"POST"
        String method = event.getMethod();

        // 获取请求处理耗时,单位:毫秒
        // 数据流动:Spring 框架自动计时 → event.getProcessingTimeMillis() → 输出日志
        long processingTimeMillis = event.getProcessingTimeMillis();

        // 获取失败原因(如果有异常)
        Throwable failureCause = event.getFailureCause();
        String failureMessage = failureCause == null ? "" : failureCause.getMessage();

        // 根据是否有异常,分别输出不同级别的日志
        if (failureCause == null) {
            // 请求成功:输出 INFO 级别日志
            log.info("clientAddress={}, requestUrl={}, method={}, costTime={}ms",
                    clientAddress, requestUrl, method, processingTimeMillis);
        } else {
            // 请求失败:输出 ERROR 级别日志,包含异常信息
            log.error("clientAddress={}, requestUrl={}, method={}, costTime={}ms, error={}",
                    clientAddress, requestUrl, method, processingTimeMillis, failureMessage);
        }
    }
}

参考资料:


6. 方案对比与总结 📋

特性 AOP 切面 自定义注解 HandlerInterceptor Micrometer + Actuator 事件监听
调用次数 ✅(自动采集)
传入参数 ✅(仅 URL 参数)
返回参数
响应耗时 ✅(自动采集)
代码入侵 需加注解 需自定义指标
生产可用 开发调试 开发调试 开发调试 ✅ 生产级 轻量监控
可视化 ❌(日志) ❌(日志) ❌(日志) ✅(Grafana) ❌(日志)
实现难度 ⭐⭐ ⭐⭐ ⭐⭐⭐

场景推荐

场景 推荐方案
开发/测试环境临时统计 AOP 切面(方案 1)
只监控关键接口 自定义注解(方案 2)
需要请求参数+返回参数完整记录 AOP 切面(方案 1)
只需监控 HTTP 请求基本信息 HandlerInterceptor(方案 3)
生产环境可视化监控大屏 Micrometer + Prometheus + Grafana(方案 4)
最轻量级、零配置 事件监听(方案 5)

实际项目建议

在实际项目中,通常组合使用多种方案:

  1. 生产环境以 Micrometer + Prometheus + Grafana 为基础监控设施,用于实时告警和历史趋势分析
  2. 开发测试阶段辅以 AOP 切面记录详细的请求参数和返回参数,方便定位问题
  3. 关键业务接口(如支付、登录)使用自定义注解进行重点监控

这种分层监控策略既能满足生产环境的稳定性要求,又能提供足够的调试信息,是大型 SpringBoot 项目的推荐实践。

参考资料:


最后更新时间:2026-05-28

相关推荐
Rust研习社1 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei4 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github