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 输出示例

css 复制代码
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,可以看到类似以下输出:

ini 复制代码
# 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 端点会额外输出自定义指标:

ini 复制代码
# 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

相关推荐
JAVA面经实录9172 小时前
MyBatis学习体系
java·mybatis
java1234_小锋2 小时前
在 Spring AI 中如何实现函数调用(Function Calling)?请说明其基本原理和应用场景。
java·人工智能·spring
苏渡苇3 小时前
Spring Cloud Alibaba:将 Sentinel 熔断限流规则持久化到 Nacos 配置中心
数据库·spring boot·mysql·spring cloud·nacos·sentinel·持久化
小马爱打代码3 小时前
Spring源码 第九篇:Spring 5 源码深度拆解 - Spring 事件驱动模型
java·后端·spring
ForgeAI码匠3 小时前
ForgeAdmin|Spring Boot 3 后台框架的自动配置设计:少写配置,多做组合
java·spring boot·后端
tongluowan0073 小时前
Redisson的参数及工作原理
java·redis·lua·分布式锁
IT_陈寒4 小时前
为什么 Java 的 Optional 让我调试到深夜?
前端·人工智能·后端
仙俊红4 小时前
Integer\int对比,equals()\hashcode面试
java·面试·职场和发展
用户8356290780514 小时前
用 Python 实现 Excel 散点图绘制与定制
后端·python