01-如何监听接口调用情况? 🔍
本文档介绍在 SpringBoot 项目中监听每个接口的调用次数、传入参数、返回参数、响应耗时的多种实现方案
章节阅读路线图 🗺️
- AOP 切面方案(最推荐) → 使用 Spring AOP 无侵入式监控所有接口
- 自定义注解方案 → 选择性监控,只针对关键接口
- HandlerInterceptor 拦截器方案 → 基于 Spring MVC 拦截器实现
- Micrometer + Actuator 生产级方案 → 接入 Prometheus + Grafana 可视化监控
- 方案对比与总结 → 根据场景选择最合适的方案
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
参考资料:
- Metrics for Your Spring REST API -- Baeldung ⭐值得阅读
- 如何为Spring Boot Actuator的http.server.requests指标添加MIN统计量 -- 火山引擎
- 实战SpringBoot结合Prometheus自定义监控指标 -- 知乎
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) |
实际项目建议
在实际项目中,通常组合使用多种方案:
- 生产环境以 Micrometer + Prometheus + Grafana 为基础监控设施,用于实时告警和历史趋势分析
- 开发测试阶段辅以 AOP 切面记录详细的请求参数和返回参数,方便定位问题
- 关键业务接口(如支付、登录)使用自定义注解进行重点监控
这种分层监控策略既能满足生产环境的稳定性要求,又能提供足够的调试信息,是大型 SpringBoot 项目的推荐实践。
参考资料:
- SpringBoot集成AOP实现接口日志记录请求参数与耗时 -- 阿里云开发者社区
- SpringBoot统计接口请求耗时 -- CSDN
- SpringBoot项目统计接口耗时功能 -- CSDN
- Spring Boot服务监控:全方位指南与实践策略 -- 百度智能云
- SpringBoot之统计接口执行耗时 -- 博客园
最后更新时间:2026-05-28