商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > SpringBoot如何监听接口调用情况

SpringBoot如何监听接口调用情况

  发布于2026-05-29 阅读(0)

扫一扫,手机访问

在SpringBoot项目中,监控接口的调用情况是个常见的需求。调用次数、传入参数、返回参数、响应耗时——这些数据对于性能分析、问题定位和业务统计都至关重要。本文将介绍几种实现方案,从轻量的日志记录到生产级的可视化监控,涵盖了大多数开发场景。 SpringBoot如何监听接口调用情况 ## 章节阅读路线图 1. **AOP 切面方案(最推荐)** → 使用 Spring AOP 无侵入式监控所有接口 2. **自定义注解方案** → 选择性监控,只针对关键接口 3. **HandlerInterceptor 拦截器方案** → 基于 Spring MVC 拦截器实现 4. **Micrometer + Actuator 生产级方案** → 接入 Prometheus + Grafana 可视化监控 5. **方案对比与总结** → 根据场景选择最合适的方案 ## 1. AOP 切面方案(最推荐) > 使用 Spring AOP 无侵入式监控所有或者指定接口,记录调用次数、参数、返回值和耗时 AOP嘛,Spring框架的核心特性之一,可以在不修改原有代码的情况下对方法进行增强。利用AOP,只需要编写一个切面类,就能统一拦截所有Controller方法,实现接口监控。这个方案最大的好处就是无侵入——现有的业务代码完全不需要改动。 ### 1.1 引入依赖 ```xml org.springframework.boot spring-boot-starter-aop com.alibaba fastjson 2.0.x ``` > spring-boot-starter-aop 已包含 AspectJ 依赖。fastjson 用于将参数和返回值序列化为 JSON 字符串,也可以用 Jackson(SpringBoot 默认自带)。 ### 1.2 编写切面类 切面类的核心逻辑并不复杂:通过环绕通知,在方法执行前后分别记录时间,再结合请求上下文提取URL、IP等基本信息,最后将数据组装输出。 ```ja va 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 ja vax.servlet.http.HttpServletRequest; // 引入 HttpServletRequest,用于获取请求 URL、方法类型等 import ja va.util.Arrays; // 引入 Arrays 工具类,用于数组操作 import ja va.util.Map; // 引入 Map 接口,用于存储调用次数统计 import ja va.util.concurrent.ConcurrentHashMap; // 引入 ConcurrentHashMap,线程安全的计数器容器 import ja va.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 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" + " 响应耗时: {}msn" + "========================================", 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: 所有接口的调用次数映射表,示例:{"UserController.getUser": 42} * * Example: * Map allStats = apiMonitorAspect.getAllCallCounts(); */ public Map getAllCallCounts() { Map 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 定义注解 首先定义一个注解,加上可选的描述信息属性: ```ja va package com.example.annotation; // 包名根据实际项目调整 import ja va.lang.annotation.ElementType; // 引入 ElementType 枚举,用于指定注解的适用范围 import ja va.lang.annotation.Retention; // 引入 Retention 枚举,用于指定注解的生命周期 import ja va.lang.annotation.RetentionPolicy; // 引入 RetentionPolicy,RetentionPolicy.RUNTIME 表示运行时保留 import ja va.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 修改切面类 切点表达式改为匹配标注了 `@Monitored` 注解的方法。 ```ja va /** * 基于自定义注解的接口监控切面 * * 功能:只对标注了 @Monitored 注解的方法进行监控 * * Args: * 无 * * Returns: * 无(通过 log 输出监控信息) * * Example: * 在 Controller 方法上添加 @Monitored 即可生效 */ @Slf4j @Aspect @Component public class MonitoredApiAspect { // 线程安全的调用次数统计容器 // Key: 接口方法名,Value: 原子计数器 private final Map 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 使用方式 在需要监控的接口方法上加一行注解即可,未标注的方法不受影响。 ```ja va @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 之前和响应返回之后执行。相比AOP方案,它的优势在于对HTTP请求的处理更原生,但缺点是只能拿到请求层面的信息,无法直接获取Controller方法的参数和返回值。 ### 3.1 实现拦截器 ```ja va 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 ja vax.servlet.http.HttpServletRequest; // 引入 HttpServletRequest import ja vax.servlet.http.HttpServletResponse; // 引入 HttpServletResponse import ja va.util.Map; // 引入 Map 接口,用于存储统计信息 import ja va.util.concurrent.ConcurrentHashMap; // 引入 ConcurrentHashMap,线程安全的统计容器 import ja va.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 urlCountMap = new ConcurrentHashMap<>(); // 使用 ThreadLocal 存储每个请求的开始时间,确保线程隔离 // 数据流动:preHandle 中 set(startTime) → afterCompletion 中 get() 计算耗时 → finally 中 remove() 清理 private final ThreadLocal startTimeHolder = new ThreadLocal<>(); // 使用 ThreadLocal 存储每个请求的 URL,用于在 afterCompletion 中统计 private final ThreadLocal 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 注册拦截器 别忘了把拦截器注册到Spring MVC的拦截器链中。 ```ja va 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 org.springframework.boot spring-boot-starter-actuator io.micrometer micrometer-registry-prometheus ``` ### 4.2 配置 application.yml 配置文件中需要暴露Prometheus端点,并启用HTTP请求的百分位数直方图。 ```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指标,还可以自定义业务指标,比如按接口记录调用次数和耗时。下面这个切面专门用来做这件事: ```ja va 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 ja va.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、方法、状态码),**无法获取传入参数和返回参数**。 ```ja va 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 发布时自动调用 * * 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 项目的推荐实践。
本文转载于:https://www.jb51.net/program/3647524vn.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注