最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot如何監(jiān)聽接口調(diào)用情況

 更新時間:2026年05月29日 08:14:58   作者:鄭恩賜  
本文檔介紹在SpringBoot項目中監(jiān)聽每個接口的調(diào)用次數(shù)、傳入?yún)?shù)、返回參數(shù)、響應(yīng)耗時的多種實現(xiàn)方案,具有一定的參考價值,感興趣的可以了解一下

本文檔介紹在 SpringBoot 項目中監(jiān)聽每個接口的調(diào)用次數(shù)、傳入?yún)?shù)、返回參數(shù)、響應(yīng)耗時的多種實現(xiàn)方案

章節(jié)閱讀路線圖 ???

  1. AOP 切面方案(最推薦) → 使用 Spring AOP 無侵入式監(jiān)控所有接口
  2. 自定義注解方案 → 選擇性監(jiān)控,只針對關(guān)鍵接口
  3. HandlerInterceptor 攔截器方案 → 基于 Spring MVC 攔截器實現(xiàn)
  4. Micrometer + Actuator 生產(chǎn)級方案 → 接入 Prometheus + Grafana 可視化監(jiān)控
  5. 方案對比與總結(jié) → 根據(jù)場景選擇最合適的方案

1. AOP 切面方案(最推薦) ??

使用 Spring AOP 無侵入式監(jiān)控所有或者指定接口,記錄調(diào)用次數(shù)、參數(shù)、返回值和耗時

AOP(Aspect Oriented Programming)是 Spring 框架的核心特性之一,它可以在不修改原有代碼的情況下,對方法進(jìn)行增強(qiáng)。利用 AOP,我們只需要編寫一個切面類,就可以統(tǒng)一攔截所有 Controller 方法,實現(xiàn)接口監(jiān)控。

1.1 引入依賴

<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 用于將參數(shù)和返回值序列化為 JSON 字符串,也可以用 Jackson(SpringBoot 默認(rèn)自帶)。

1.2 編寫切面類

package com.example.aspect;                                  // 包名根據(jù)實際項目調(diào)整
import com.alibaba.fastjson.JSON;                            // 引入 JSON 序列化工具,用于格式化參數(shù)和返回值
import lombok.extern.slf4j.Slf4j;                            // 引入 Slf4j 注解,自動生成 log 日志對象
import org.aspectj.lang.ProceedingJoinPoint;                 // 引入 ProceedingJoinPoint,用于執(zhí)行目標(biāo)方法并獲取上下文
import org.aspectj.lang.annotation.Around;                   // 引入環(huán)繞通知注解,@Around 可以包裹目標(biāo)方法的執(zhí)行
import org.aspectj.lang.annotation.Aspect;                   // 引入切面注解,標(biāo)識該類為 AOP 切面
import org.aspectj.lang.annotation.Pointcut;                 // 引入切點注解,定義攔截規(guī)則
import org.springframework.stereotype.Component;              // 引入 Component 注解,將切面注入 Spring 容器
import org.springframework.web.context.request.RequestContextHolder;        // 引入 RequestContextHolder,獲取當(dāng)前請求的上下文
import org.springframework.web.context.request.ServletRequestAttributes;    // 引入 ServletRequestAttributes,獲取 HttpServletRequest
import javax.servlet.http.HttpServletRequest;                // 引入 HttpServletRequest,用于獲取請求 URL、方法類型等
import java.util.Arrays;                                     // 引入 Arrays 工具類,用于數(shù)組操作
import java.util.Map;                                        // 引入 Map 接口,用于存儲調(diào)用次數(shù)統(tǒng)計
import java.util.concurrent.ConcurrentHashMap;               // 引入 ConcurrentHashMap,線程安全的計數(shù)器容器
import java.util.concurrent.atomic.AtomicLong;               // 引入 AtomicLong,線程安全的原子計數(shù)器
/**
 * 接口監(jiān)控切面
 *
 * 功能:統(tǒng)計每個接口的調(diào)用次數(shù)、記錄傳入?yún)?shù)、返回參數(shù)、響應(yīng)耗時
 *
 * Args:
 *     無
 *
 * Returns:
 *     無(通過 log 輸出監(jiān)控信息)
 *
 * Example:
 *     自動生效,無需手動調(diào)用
 */
@Slf4j                                      // Lombok 注解,自動生成 log 字段,示例:log.info("消息")
@Aspect                                     // 標(biāo)識當(dāng)前類為切面類
@Component                                  // 將當(dāng)前類注入 Spring 容器
public class ApiMonitorAspect {
    // 線程安全的調(diào)用次數(shù)統(tǒng)計容器
    // Key: 接口方法名(類名+方法名),Value: 原子計數(shù)器
    // 示例:{"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() {
        // 切點方法,無實際邏輯,僅用于定義攔截規(guī)則
    }
    /**
     * 環(huán)繞通知:在目標(biāo)方法執(zhí)行前后插入監(jiān)控邏輯
     *
     * Args:
     *     joinPoint: 連接點對象,包含目標(biāo)方法的全部上下文信息
     *
     * Returns:
     *     Object: 目標(biāo)方法的返回值,原封不動返回
     *
     * Example:
     *     @Around("controllerPointcut()")
     *     public Object monitorApi(ProceedingJoinPoint joinPoint)
     */
    @Around("controllerPointcut()")
    public Object monitorApi(ProceedingJoinPoint joinPoint) throws Throwable {
        // ===== 1. 獲取請求上下文信息 =====
        // 通過 RequestContextHolder 獲取當(dāng)前線程綁定的 Servlet 請求屬性
        // 數(shù)據(jù)流動: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. 獲取方法信息 =====
        // 獲取目標(biāo)方法的全限定名(包名+類名+方法名)
        // 數(shù)據(jù)流動:joinPoint.getSignature() → 方法簽名 → 類名.方法名
        String className = joinPoint.getTarget().getClass().getSimpleName();           // 獲取類名,示例:"UserController"
        String methodName = className + "." + joinPoint.getSignature().getName();     // 組裝方法名,示例:"UserController.getUser"
        // ===== 3. 統(tǒng)計調(diào)用次數(shù) =====
        // 從 callCountMap 中獲取或創(chuàng)建該方法的原子計數(shù)器,然后自增
        // 數(shù)據(jù)流動:methodName → ConcurrentHashMap.get() → AtomicLong.incrementAndGet() → 返回遞增后的值
        long currentCount = callCountMap.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();
        // ===== 4. 獲取傳入?yún)?shù) =====
        // 獲取目標(biāo)方法的所有參數(shù)值列表
        // 數(shù)據(jù)流動:joinPoint.getArgs() → Object[] → JSON.toJSONString() → JSON 字符串
        Object[] args = joinPoint.getArgs();
        String requestParams = JSON.toJSONString(args);           // 將參數(shù)數(shù)組序列化為 JSON,示例:"[{"id":1,"name":"張三"}]"
        // ===== 5. 記錄開始時間 =====
        // 記錄當(dāng)前時間戳,用于后續(xù)計算接口耗時
        long startTime = System.currentTimeMillis();
        // ===== 6. 執(zhí)行目標(biāo)方法 =====
        // proceed() 會執(zhí)行被攔截的原始方法,返回原始結(jié)果
        // 數(shù)據(jù)流動:joinPoint.proceed() → 目標(biāo)方法執(zhí)行 → 返回 Object 類型結(jié)果
        Object result = joinPoint.proceed();
        // ===== 7. 計算響應(yīng)耗時 =====
        // 用結(jié)束時間減去開始時間,得到接口處理耗時
        long elapsed = System.currentTimeMillis() - startTime;    // 計算耗時,單位:毫秒
        // ===== 8. 獲取返回參數(shù) =====
        // 將方法返回值序列化為 JSON 字符串
        // 數(shù)據(jù)流動:result Object → JSON.toJSONString() → JSON 字符串
        String responseResult = JSON.toJSONString(result);
        // ===== 9. 輸出監(jiān)控日志 =====
        // 匯總所有監(jiān)控信息,統(tǒng)一輸出
        log.info("\n========================================\n" +
                 "接口監(jiān)控信息:\n" +
                 "  請求地址: {} {}\n" +
                 "  客戶端IP: {}\n" +
                 "  接口方法: {}\n" +
                 "  調(diào)用次數(shù)(累計): {}\n" +
                 "  傳入?yún)?shù): {}\n" +
                 "  返回參數(shù): {}\n" +
                 "  響應(yīng)耗時: {}ms\n" +
                 "========================================",
                httpMethod, requestUrl,                          // 填充請求地址
                clientIp,                                        // 填充客戶端 IP
                methodName,                                      // 填充接口方法名
                currentCount,                                    // 填充累計調(diào)用次數(shù)
                requestParams.length() > 500 ? requestParams.substring(0, 500) + "..." : requestParams,  // 參數(shù)過長時截斷
                responseResult.length() > 500 ? responseResult.substring(0, 500) + "..." : responseResult,  // 返回值過長時截斷
                elapsed);                                        // 填充響應(yīng)耗時
        // 原封不動返回目標(biāo)方法的執(zhí)行結(jié)果,不對業(yè)務(wù)邏輯產(chǎn)生任何影響
        return result;
    }
    /**
     * 獲取指定接口的累計調(diào)用次數(shù)
     *
     * Args:
     *     methodName: 方法名,格式為 "類名.方法名",示例:"UserController.getUser"
     *
     * Returns:
     *     long: 該接口的累計調(diào)用次數(shù),如果未找到返回 0
     *
     * Example:
     *     long count = apiMonitorAspect.getCallCount("UserController.getUser");
     */
    public long getCallCount(String methodName) {
        AtomicLong counter = callCountMap.get(methodName);       // 從 Map 中查詢指定方法的計數(shù)器
        return counter != null ? counter.get() : 0;              // 如果存在則返回計數(shù),否則返回 0
    }
    /**
     * 獲取所有接口的調(diào)用次數(shù)統(tǒng)計(可用于對外暴露 API)
     *
     * Args:
     *     無
     *
     * Returns:
     *     Map<String, Long>: 所有接口的調(diào)用次數(shù)映射表,示例:{"UserController.getUser": 42}
     *
     * Example:
     *     Map<String, Long> allStats = apiMonitorAspect.getAllCallCounts();
     */
    public Map<String, Long> getAllCallCounts() {
        Map<String, Long> result = new ConcurrentHashMap<>();    // 創(chuàng)建結(jié)果容器
        callCountMap.forEach((key, value) -> result.put(key, value.get()));  // 遍歷并轉(zhuǎn)換 AtomicLong 為普通 Long
        return result;                                           // 返回不可修改的統(tǒng)計視圖
    }
}

1.3 輸出示例

2026-05-28 14:30:22.156 [http-nio-8080-exec-1] INFO  c.e.aspect.ApiMonitorAspect -
========================================
接口監(jiān)控信息:
  請求地址: GET http://localhost:8080/api/user/list
  客戶端IP: 127.0.0.1
  接口方法: UserController.getUserList
  調(diào)用次數(shù)(累計): 1
  傳入?yún)?shù): [{"page":1,"size":10}]
  返回參數(shù): {"code":200,"data":[{"id":1,"name":"張三"}],"msg":"成功"}
  響應(yīng)耗時: 45ms
========================================

2026-05-28 14:30:25.891 [http-nio-8080-exec-2] INFO  c.e.aspect.ApiMonitorAspect -
========================================
接口監(jiān)控信息:
  請求地址: POST http://localhost:8080/api/user/create
  客戶端IP: 192.168.1.100
  接口方法: UserController.createUser
  調(diào)用次數(shù)(累計): 1
  傳入?yún)?shù): [{"name":"李四","age":25,"email":"lisi@example.com"}]
  返回參數(shù): {"code":200,"data":{"id":2},"msg":"創(chuàng)建成功"}
  響應(yīng)耗時: 120ms
========================================

從輸出可以看到,監(jiān)控信息完整覆蓋了需求中的四個維度:

  • 調(diào)用次數(shù):調(diào)用次數(shù)(累計): 1
  • 傳入?yún)?shù):傳入?yún)?shù): [{"page":1,"size":10}]
  • 返回參數(shù):返回參數(shù): {"code":200,"data":[...]}
  • 響應(yīng)耗時:響應(yīng)耗時: 45ms

2. 自定義注解方案(選擇性監(jiān)控) ???

通過自定義注解實現(xiàn)選擇性監(jiān)控,只對關(guān)鍵接口生效

如果不想監(jiān)控所有接口,可以自定義一個注解,只有在方法上添加了該注解的接口才進(jìn)行監(jiān)控。

2.1 定義注解

package com.example.annotation;                               // 包名根據(jù)實際項目調(diào)整
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 注解,用于限制注解能標(biāo)注的位置
/**
 * 接口監(jiān)控注解
 *
 * 用于標(biāo)記需要監(jiān)控調(diào)用次數(shù)、參數(shù)、耗時等信息的接口方法
 *
 * Args:
 *     value: 接口描述信息,可選,示例:"用戶登錄接口"
 *
 * Returns:
 *     無(注解本身不返回值)
 *
 * Example:
 *     @Monitored("用戶登錄接口")
 *     public Result login(@RequestBody LoginDTO dto)
 */
@Target(ElementType.METHOD)                  // 注解只能標(biāo)注在方法上
@Retention(RetentionPolicy.RUNTIME)          // 注解在運行時保留,AOP 才能在運行時讀取
public @interface Monitored {
    /**
     * 接口描述信息
     *
     * Args:
     *     無
     *
     * Returns:
     *     String: 接口的描述說明,默認(rèn)為空字符串
     *
     * Example:
     *     @Monitored("用戶登錄接口")
     */
    String value() default "";               // 可選屬性,用于描述接口用途
}

2.2 修改切面類

/**
 * 基于自定義注解的接口監(jiān)控切面
 *
 * 功能:只對標(biāo)注了 @Monitored 注解的方法進(jìn)行監(jiān)控
 *
 * Args:
 *     無
 *
 * Returns:
 *     無(通過 log 輸出監(jiān)控信息)
 *
 * Example:
 *     在 Controller 方法上添加 @Monitored 即可生效
 */
@Slf4j
@Aspect
@Component
public class MonitoredApiAspect {

    // 線程安全的調(diào)用次數(shù)統(tǒng)計容器
    // Key: 接口方法名,Value: 原子計數(shù)器
    private final Map<String, AtomicLong> callCountMap = new ConcurrentHashMap<>();

    /**
     * 定義切點:攔截所有標(biāo)注了 @Monitored 注解的方法
     *
     * Args:
     *     monitored: 注入的注解實例,可在通知中讀取注解屬性
     *
     * Returns:
     *     無
     *
     * Example:
     *     @annotation(com.example.annotation.Monitored) 匹配所有標(biāo)有 @Monitored 的方法
     */
    @Pointcut("@annotation(com.example.annotation.Monitored)")
    public void monitoredPointcut() {
        // 切點方法,無實際邏輯
    }

    /**
     * 環(huán)繞通知:攔截并監(jiān)控標(biāo)注了 @Monitored 的方法
     *
     * Args:
     *     joinPoint: 連接點對象,包含目標(biāo)方法的全部上下文信息
     *     monitored: 目標(biāo)方法上的 @Monitored 注解實例,可讀取 value 屬性
     *
     * Returns:
     *     Object: 目標(biāo)方法的返回值
     *
     * 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();

        // 統(tǒng)計調(diào)用次數(shù)
        long currentCount = callCountMap.computeIfAbsent(methodName, k -> new AtomicLong(0)).incrementAndGet();

        // 獲取傳入?yún)?shù)
        Object[] args = joinPoint.getArgs();
        String requestParams = JSON.toJSONString(args);

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

        // 記錄開始時間
        long startTime = System.currentTimeMillis();

        // 執(zhí)行目標(biāo)方法
        Object result = joinPoint.proceed();

        // 計算耗時
        long elapsed = System.currentTimeMillis() - startTime;

        // 輸出監(jiān)控日志
        log.info("[接口監(jiān)控] {} | {} | 方法: {} | 累計調(diào)用: {} | 參數(shù): {} | 耗時: {}ms | 返回: {}",
                request.getMethod(), request.getRequestURL(),
                methodName, currentCount,
                requestParams, elapsed,
                JSON.toJSONString(result));

        return result;
    }
}

2.3 使用方式

@RestController
@RequestMapping("/api/user")
public class UserController {

    @Monitored("用戶登錄接口")              // 標(biāo)注需要監(jiān)控的方法
    @PostMapping("/login")
    public Result login(@RequestBody LoginDTO loginDTO) {
        // 業(yè)務(wù)邏輯
        return Result.success("登錄成功");
    }

    @GetMapping("/list")                    // 未標(biāo)注 @Monitored,不會被監(jiān)控
    public Result list() {
        return Result.success(userService.list());
    }
}

3. HandlerInterceptor 攔截器方案 ??

使用 Spring MVC 的 HandlerInterceptor,在請求處理前后插入監(jiān)控邏輯

Interceptor 是 Spring MVC 特有的攔截機(jī)制,在請求進(jìn)入 Controller 之前和響應(yīng)返回之后執(zhí)行。適合只需要監(jiān)控 HTTP 請求的場景。

3.1 實現(xiàn)攔截器

package com.example.interceptor;                             // 包名根據(jù)實際項目調(diào)整

import com.alibaba.fastjson.JSON;                            // 引入 JSON 序列化工具,用于格式化請求參數(shù)
import lombok.extern.slf4j.Slf4j;                            // 引入 Slf4j 注解
import org.springframework.stereotype.Component;              // 引入 Component 注解,將攔截器注入 Spring 容器
import org.springframework.web.servlet.HandlerInterceptor;   // 引入 HandlerInterceptor 接口,需要實現(xiàn) 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 接口,用于存儲統(tǒng)計信息
import java.util.concurrent.ConcurrentHashMap;               // 引入 ConcurrentHashMap,線程安全的統(tǒng)計容器
import java.util.concurrent.atomic.AtomicLong;               // 引入 AtomicLong,線程安全的計數(shù)器

/**
 * 接口監(jiān)控攔截器
 *
 * 功能:在請求處理前后記錄調(diào)用次數(shù)、參數(shù)、耗時等監(jiān)控信息
 *
 * Args:
 *     無
 *
 * Returns:
 *     無(通過 log 輸出監(jiān)控信息)
 *
 * Example:
 *     在 WebConfig 中注冊該攔截器即可生效
 */
@Slf4j
@Component
public class ApiMonitorInterceptor implements HandlerInterceptor {

    // 線程安全的統(tǒng)計容器
    // Key: URL(請求路徑),Value: 原子計數(shù)器
    // 示例:{"/api/user/list": 42, "/api/user/create": 15}
    private final Map<String, AtomicLong> urlCountMap = new ConcurrentHashMap<>();

    // 使用 ThreadLocal 存儲每個請求的開始時間,確保線程隔離
    // 數(shù)據(jù)流動:preHandle 中 set(startTime) → afterCompletion 中 get() 計算耗時 → finally 中 remove() 清理
    private final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();

    // 使用 ThreadLocal 存儲每個請求的 URL,用于在 afterCompletion 中統(tǒng)計
    private final ThreadLocal<String> urlHolder = new ThreadLocal<>();

    /**
     * 請求處理前執(zhí)行
     *
     * Args:
     *     request: HTTP 請求對象
     *     response: HTTP 響應(yīng)對象
     *     handler: 處理該請求的處理器(Controller 方法)
     *
     * Returns:
     *     boolean: true 表示繼續(xù)執(zhí)行,false 表示中斷請求
     *
     * Example:
     *     preHandle 中記錄開始時間和請求參數(shù)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 獲取請求 URL,示例:"/api/user/list"
        String requestUrl = request.getRequestURI();

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

        // 該請求的調(diào)用次數(shù)加 1
        // 數(shù)據(jù)流動:urlCountMap.get(requestUrl) → AtomicLong.incrementAndGet() → 返回自增后的值
        long currentCount = urlCountMap.computeIfAbsent(requestUrl, k -> new AtomicLong(0)).incrementAndGet();

        // 記錄請求開始時間,存入 ThreadLocal
        startTimeHolder.set(System.currentTimeMillis());

        // 記錄請求信息
        log.info("[請求開始] {} {} | 累計調(diào)用: {} | 參數(shù): {}",
                request.getMethod(),                         // HTTP 方法,示例:"GET"
                requestUrl,                                  // 請求路徑,示例:"/api/user/list"
                currentCount,                                // 累計調(diào)用次數(shù)
                JSON.toJSONString(request.getParameterMap()) // 請求參數(shù) Map,示例:{"page":["1"],"size":["10"]}
        );

        return true;  // 返回 true,繼續(xù)執(zhí)行后續(xù)處理
    }

    /**
     * 請求完成后執(zhí)行(無論是否異常)
     *
     * Args:
     *     request: HTTP 請求對象
     *     response: HTTP 響應(yīng)對象
     *     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 未執(zhí)行成功
                return;                                       // 直接返回,不進(jìn)行后續(xù)處理
            }

            // 計算處理耗時,單位:毫秒
            // 數(shù)據(jù)流動:結(jié)束時間戳 - 開始時間戳 → 耗時毫秒數(shù)
            long elapsed = System.currentTimeMillis() - startTime;

            // 獲取請求 URL
            String requestUrl = urlHolder.get();

            // 獲取 HTTP 狀態(tài)碼,示例:200、404、500
            int status = response.getStatus();

            // 輸出監(jiān)控日志
            log.info("[請求結(jié)束] {} | 狀態(tài): {} | 耗時: {}ms{}",
                    requestUrl,                              // 請求路徑
                    status,                                  // HTTP 狀態(tài)碼
                    elapsed,                                 // 響應(yīng)耗時
                    ex != null ? " | 異常: " + ex.getMessage() : ""  // 如果有異常則追加異常信息
            );
        } finally {
            // 清理 ThreadLocal,防止內(nèi)存泄漏(線程池中的線程會被復(fù)用)
            startTimeHolder.remove();
            urlHolder.remove();
        }
    }
}

3.2 注冊攔截器

package com.example.config;                                  // 包名根據(jù)實際項目調(diào)整

import com.example.interceptor.ApiMonitorInterceptor;         // 引入自定義攔截器
import org.springframework.beans.factory.annotation.Autowired;  // 引入自動注入注解
import org.springframework.context.annotation.Configuration;   // 引入 Configuration 注解,標(biāo)識為配置類
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                                                              // 標(biāo)識為配置類
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private ApiMonitorInterceptor apiMonitorInterceptor;                     // 注入自定義的接口監(jiān)控攔截器

    /**
     * 添加攔截器到 Spring MVC 框架
     *
     * Args:
     *     registry: 攔截器注冊器,用于添加和配置攔截器
     *
     * Returns:
     *     無
     *
     * Example:
     *     registry.addInterceptor(interceptor).addPathPatterns("/**");
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiMonitorInterceptor)                       // 添加自定義攔截器
                .addPathPatterns("/**");                                     // 攔截所有請求路徑
    }
}

4. Micrometer + Actuator 生產(chǎn)級方案 ??

使用 Micrometer 指標(biāo)框架 + Spring Actuator 暴露指標(biāo),接入 Prometheus + Grafana 實現(xiàn)可視化監(jiān)控

前面的方案適合開發(fā)調(diào)試和簡單的日志監(jiān)控,但在生產(chǎn)環(huán)境中,我們需要持久化的指標(biāo)存儲和可視化看板。Micrometer 是 Spring Boot 官方推薦的指標(biāo)采集框架,可以無縫對接 Prometheus、InfluxDB 等監(jiān)控系統(tǒng)。

4.1 引入依賴

<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

spring:
  application:
    name: my-api-service                                          # 應(yīng)用名稱,作為 Prometheus 指標(biāo)的標(biāo)簽
management:
  endpoints:
    web:
      exposure:
        include: prometheus,metrics,health                        # 暴露 Prometheus、Metrics、Health 端點
  metrics:
    tags:
      application: ${spring.application.name}                     # 為所有指標(biāo)添加 application 標(biāo)簽
    distribution:
      percentiles-histogram:
        http.server.requests: true                                # 為 HTTP 請求指標(biāo)啟用百分位數(shù)直方圖
      slo:
        http.server.requests: 10ms, 50ms, 100ms, 200ms, 500ms, 1s  # 定義服務(wù)級別目標(biāo)(SLO)的耗時邊界

4.3 驗證指標(biāo)端點

啟動應(yīng)用后,訪問 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

指標(biāo)含義:

指標(biāo)含義
http_server_requests_seconds_count接口調(diào)用總次數(shù)
http_server_requests_seconds_sum接口調(diào)用累計耗時(秒)
http_server_requests_seconds_max接口調(diào)用最大耗時(秒)

4.4 自定義業(yè)務(wù)指標(biāo)

除了框架自動采集的 HTTP 指標(biāo),還可以自定義業(yè)務(wù)指標(biāo),例如按接口記錄詳細(xì)參數(shù):

package com.example.metrics;                                   // 包名根據(jù)實際項目調(diào)整
import io.micrometer.core.instrument.Counter;                  // 引入 Micrometer 計數(shù)器,用于統(tǒng)計調(diào)用次數(shù)
import io.micrometer.core.instrument.MeterRegistry;            // 引入 MeterRegistry,Micrometer 指標(biāo)注冊中心
import io.micrometer.core.instrument.Timer;                    // 引入 Micrometer 計時器,用于統(tǒng)計耗時
import lombok.extern.slf4j.Slf4j;                              // 引入 Slf4j 注解
import org.aspectj.lang.ProceedingJoinPoint;                   // 引入 ProceedingJoinPoint
import org.aspectj.lang.annotation.Around;                     // 引入環(huán)繞通知注解
import org.aspectj.lang.annotation.Aspect;                     // 引入切面注解
import org.aspectj.lang.annotation.Pointcut;                   // 引入切點注解
import org.springframework.stereotype.Component;                // 引入 Component 注解
import java.util.concurrent.TimeUnit;                          // 引入 TimeUnit,用于時間單位轉(zhuǎn)換
/**
 * Micrometer 自定義指標(biāo)切面
 *
 * 功能:統(tǒng)計每個接口的調(diào)用次數(shù)和耗時,以 Prometheus 指標(biāo)格式暴露
 *
 * Args:
 *     無
 *
 * Returns:
 *     無(指標(biāo)數(shù)據(jù)自動注冊到 MeterRegistry)
 *
 * Example:
 *     結(jié)合 Actuator + Prometheus + Grafana 實現(xiàn)可視化監(jiān)控
 */
@Slf4j
@Aspect
@Component
public class MicrometerMetricsAspect {
    private final MeterRegistry meterRegistry;                  // Micrometer 指標(biāo)注冊中心,用于創(chuàng)建和管理指標(biāo)
    /**
     * 構(gòu)造方法注入 MeterRegistry
     *
     * Args:
     *     meterRegistry: Spring Boot 自動配置的 MeterRegistry 實例
     *
     * Returns:
     *     無
     */
    public MicrometerMetricsAspect(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    /**
     * 定義切點:攔截所有 Controller 方法
     */
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerPointcut() {}
    /**
     * 環(huán)繞通知:采集接口調(diào)用次數(shù)和耗時指標(biāo)
     *
     * Args:
     *     joinPoint: 連接點對象
     *
     * Returns:
     *     Object: 目標(biāo)方法的返回值
     */
    @Around("controllerPointcut()")
    public Object recordMetrics(ProceedingJoinPoint joinPoint) throws Throwable {
        // 獲取接口名稱,用于指標(biāo)標(biāo)簽
        String methodName = joinPoint.getTarget().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();
        // 創(chuàng)建計時器:統(tǒng)計該接口的耗時分布
        // 數(shù)據(jù)流動:Timer.Sample 記錄開始 → 方法執(zhí)行 → sample.stop() 記錄結(jié)束 → 指標(biāo)注冊到 Prometheus
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            // 執(zhí)行目標(biāo)方法
            Object result = joinPoint.proceed();
            return result;
        } finally {
            // 停止計時器,并將指標(biāo)注冊到 MeterRegistry
            // 數(shù)據(jù)流動:sample.stop(timer) → /actuator/prometheus 暴露 → Prometheus 采集 → Grafana 展示
            sample.stop(Timer.builder("api.call.duration")           // 指標(biāo)名稱
                    .description("接口調(diào)用耗時")                       // 指標(biāo)描述
                    .tag("method", methodName)                        // 標(biāo)簽:接口方法名
                    .tag("class", joinPoint.getTarget().getClass().getSimpleName())  // 標(biāo)簽:類名
                    .register(meterRegistry));                        // 注冊到 MeterRegistry
            // 增加調(diào)用次數(shù)計數(shù)器
            // 數(shù)據(jù)流動:Counter.increment() → 指標(biāo)值 +1 → Prometheus 采集
            Counter counter = Counter.builder("api.call.count")      // 指標(biāo)名稱
                    .description("接口調(diào)用次數(shù)")                        // 指標(biāo)描述
                    .tag("method", methodName)                        // 標(biāo)簽:接口方法名
                    .register(meterRegistry);                        // 注冊到 MeterRegistry
            counter.increment();                                      // 計數(shù)器加 1
        }
    }
}

配置完成后,/actuator/prometheus 端點會額外輸出自定義指標(biāo):

# HELP api_call_duration_seconds 接口調(diào)用耗時
# 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 接口調(diào)用次數(shù)
# TYPE api_call_count_total counter
api_call_count_total{method="UserController.getUserList",} 5.0

5. 基于事件監(jiān)聽方案 ??

使用 Spring 框架的 ServletRequestHandledEvent 事件監(jiān)聽 HTTP 請求處理完成事件

Spring 框架在每次 HTTP 請求處理完成后會發(fā)布 ServletRequestHandledEvent 事件,包含了請求的詳細(xì)信息。只需監(jiān)聽該事件即可獲取接口耗時,無需任何切面或攔截器配置。

注意:此方案只能獲取耗時和基本請求信息(URL、方法、狀態(tài)碼),無法獲取傳入?yún)?shù)和返回參數(shù)。

package com.example.listener;                                  // 包名根據(jù)實際項目調(diào)整
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;  // 引入請求處理事件類
/**
 * 請求耗時事件監(jiān)聽器
 *
 * 功能:監(jiān)聽 ServletRequestHandledEvent 事件,記錄每個接口的調(diào)用耗時
 *       注意:此方案無法獲取請求參數(shù)和返回參數(shù),僅能獲取 URL、方法、耗時
 *
 * Args:
 *     無
 *
 * Returns:
 *     無(通過 log 輸出監(jiān)控信息)
 *
 * Example:
 *     自動生效,無需額外配置
 */
@Slf4j
@Component
public class RequestTimeEventListener implements ApplicationListener<ServletRequestHandledEvent> {
    /**
     * 事件處理方法:ServletRequestHandledEvent 發(fā)布時自動調(diào)用
     *
     * Args:
     *     event: ServletRequestHandledEvent 事件對象,包含請求的 URL、方法、耗時、客戶端地址等信息
     *
     * Returns:
     *     無
     *
     * Example:
     *     請求完成后自動觸發(fā),輸出:
     *     "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();
        // 獲取請求處理耗時,單位:毫秒
        // 數(shù)據(jù)流動:Spring 框架自動計時 → event.getProcessingTimeMillis() → 輸出日志
        long processingTimeMillis = event.getProcessingTimeMillis();
        // 獲取失敗原因(如果有異常)
        Throwable failureCause = event.getFailureCause();
        String failureMessage = failureCause == null ? "" : failureCause.getMessage();
        // 根據(jù)是否有異常,分別輸出不同級別的日志
        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. 方案對比與總結(jié) ??

特性AOP 切面自定義注解HandlerInterceptorMicrometer + Actuator事件監(jiān)聽
調(diào)用次數(shù)????(自動采集)?
傳入?yún)?shù)???(僅 URL 參數(shù))??
返回參數(shù)?????
響應(yīng)耗時????(自動采集)?
代碼入侵需加注解需自定義指標(biāo)
生產(chǎn)可用開發(fā)調(diào)試開發(fā)調(diào)試開發(fā)調(diào)試? 生產(chǎn)級輕量監(jiān)控
可視化?(日志)?(日志)?(日志)?(Grafana)?(日志)
實現(xiàn)難度?????????

場景推薦

場景推薦方案
開發(fā)/測試環(huán)境臨時統(tǒng)計AOP 切面(方案 1)
只監(jiān)控關(guān)鍵接口自定義注解(方案 2)
需要請求參數(shù)+返回參數(shù)完整記錄AOP 切面(方案 1)
只需監(jiān)控 HTTP 請求基本信息HandlerInterceptor(方案 3)
生產(chǎn)環(huán)境可視化監(jiān)控大屏Micrometer + Prometheus + Grafana(方案 4)
最輕量級、零配置事件監(jiān)聽(方案 5)

實際項目建議

在實際項目中,通常組合使用多種方案:

  1. 生產(chǎn)環(huán)境以 Micrometer + Prometheus + Grafana 為基礎(chǔ)監(jiān)控設(shè)施,用于實時告警和歷史趨勢分析
  2. 開發(fā)測試階段輔以 AOP 切面記錄詳細(xì)的請求參數(shù)和返回參數(shù),方便定位問題
  3. 關(guān)鍵業(yè)務(wù)接口(如支付、登錄)使用自定義注解進(jìn)行重點監(jiān)控

這種分層監(jiān)控策略既能滿足生產(chǎn)環(huán)境的穩(wěn)定性要求,又能提供足夠的調(diào)試信息,是大型 SpringBoot 項目的推薦實踐。

到此這篇關(guān)于SpringBoot如何監(jiān)聽接口調(diào)用情況的文章就介紹到這了,更多相關(guān)SpringBoot 監(jiān)聽接口調(diào)用情況內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java獲取鼠標(biāo)在屏幕上坐標(biāo)的方法

    java獲取鼠標(biāo)在屏幕上坐標(biāo)的方法

    本文介紹的是如何用java獲取鼠標(biāo)在屏幕上的坐標(biāo),這個功能其實沒什么難度可言,在這里分享給大家,是因為有時候我們需要這個工具,Java作為跨平臺語言的優(yōu)勢在這個軟件就可以體現(xiàn)出來,不需修改就可以在windows、mac、Linux上使用這個軟件。下面來一起看看詳細(xì)的介紹吧。
    2016-12-12
  • 使用MockMvc進(jìn)行controller層單元測試 事務(wù)自動回滾的完整案例

    使用MockMvc進(jìn)行controller層單元測試 事務(wù)自動回滾的完整案例

    這篇文章主要介紹了使用MockMvc進(jìn)行controller層單元測試 事務(wù)自動回滾的完整案例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringBoot 如何實現(xiàn)異步編程

    SpringBoot 如何實現(xiàn)異步編程

    在SpringBoot的日常開發(fā)中,一般都是同步調(diào)用的,但實際中有很多場景非常適合使用異步來處理,本文就詳細(xì)的介紹一下SpringBoot 如何實現(xiàn)異步編程 ,具有一定的參考價值,感興趣的可以了解一下
    2021-12-12
  • SpringBoot中四種AOP實戰(zhàn)應(yīng)用場景及代碼實現(xiàn)

    SpringBoot中四種AOP實戰(zhàn)應(yīng)用場景及代碼實現(xiàn)

    面向切面編程(AOP)是Spring框架的核心功能之一,它通過預(yù)編譯和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護(hù),在SpringBoot應(yīng)用中,AOP能夠幫助我們優(yōu)雅地解決橫切關(guān)注點問題,本文將介紹SpringBoot中4種AOP實戰(zhàn)應(yīng)用場景,需要的朋友可以參考下
    2025-05-05
  • SpringBoot2.6.13+Java8跨域配置實踐

    SpringBoot2.6.13+Java8跨域配置實踐

    文章介紹了三種跨域配置方案,并對比了它們的優(yōu)缺點,方案一和方案二適用于開發(fā)環(huán)境,方案三適用于生產(chǎn)環(huán)境,如果使用SpringSecurity,推薦使用方案二,如果需要攜帶憑證,將allowCredentials設(shè)置為true
    2026-03-03
  • SpringBoot實現(xiàn)WebSocket即時通訊的示例代碼

    SpringBoot實現(xiàn)WebSocket即時通訊的示例代碼

    本文主要介紹了SpringBoot實現(xiàn)WebSocket即時通訊的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 數(shù)據(jù)庫阿里連接池 druid配置詳解

    數(shù)據(jù)庫阿里連接池 druid配置詳解

    本篇文章主要介紹了數(shù)據(jù)庫阿里連接池 druid配置詳解,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • eclipse springboot工程打war包方法及再Tomcat中運行的方法

    eclipse springboot工程打war包方法及再Tomcat中運行的方法

    這篇文章主要介紹了eclipse springboot工程打war包方法及再Tomcat中運行的方法,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • SpringBoot實現(xiàn)文件訪問安全的方法詳解

    SpringBoot實現(xiàn)文件訪問安全的方法詳解

    在Web應(yīng)用開發(fā)中,文件上傳、下載和讀取功能是常見需求,然而,不安全的文件訪問實現(xiàn)可能導(dǎo)致嚴(yán)重的任意文件讀取/寫入漏洞,下面我們就來看看SpringBoot實現(xiàn)文件訪問安全的相關(guān)方法吧
    2025-12-12
  • SpringBoot中手動開啟事務(wù)的實現(xiàn)方法

    SpringBoot中手動開啟事務(wù)的實現(xiàn)方法

    在Spring Boot中,除了使用@Transactional注解外,還可以通過TransactionTemplate或PlatformTransactionManager手動控制事務(wù),本文給大家介紹SpringBoot中如何手動開啟事務(wù),感興趣的朋友跟隨小編一起看看吧
    2025-11-11

最新評論

涿鹿县| 东乌珠穆沁旗| 房产| 天台县| 宁德市| 卢龙县| 岑巩县| 电白县| 德昌县| 利津县| 太仆寺旗| 崇明县| 察哈| 广水市| 巢湖市| 姚安县| 武夷山市| 汶川县| 苗栗市| 大厂| 永春县| 新河县| 同心县| 六盘水市| 江门市| 弋阳县| 保亭| 泸定县| 镇远县| 徐汇区| 吴江市| 通化市| 连平县| 页游| 香格里拉县| 无棣县| 新田县| 达尔| 鸡西市| 娄烦县| 高州市|