Spring Boot 接口耗时统计

核心基于 Spring AOP 实现,无侵入、低开销

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Aspect
@Component
public class ApiTimeConsumeAspect {

    @Around("execution(@(org.springframework.web.bind.annotation.*Mapping) * *(..))")
    public Object recordApiTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String fullApiUrl = getFullApiUrl(joinPoint);

        List<String> excludeUrls = Arrays.asList("/account/getCurrent");
        boolean isExclude = excludeUrls.stream().anyMatch(url -> fullApiUrl.startsWith(url));
        if (isExclude) {
            return joinPoint.proceed();
        }

        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long costTime = System.currentTimeMillis() - startTime;

        log.info("接口耗时统计 -> url:{},耗时:{}ms", fullApiUrl, costTime);

        if (costTime > 5000) {
            log.warn("【慢接口警告】接口url:{},耗时:{}ms", fullApiUrl, costTime);
        }

        return result;
    }

    private String getFullApiUrl(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> clazz = signature.getDeclaringType(); 
        Method method = signature.getMethod();

        String classPath = resolveClassRequestMappingPath(clazz);
        String methodPath = resolveMethodRequestMappingPath(method);

        return combinePath(classPath, methodPath);
    }

    private String resolveClassRequestMappingPath(Class<?> clazz) {
        RequestMapping ann = clazz.getAnnotation(RequestMapping.class);
        if (ann != null && ann.value().length > 0) {
            return ann.value()[0].trim();
        }
        return "";
    }

    private String resolveMethodRequestMappingPath(Method method) {
        if (method.isAnnotationPresent(GetMapping.class)) {
            return firstValue(method.getAnnotation(GetMapping.class).value());
        } else if (method.isAnnotationPresent(PostMapping.class)) {
            return firstValue(method.getAnnotation(PostMapping.class).value());
        } else if (method.isAnnotationPresent(PutMapping.class)) {
            return firstValue(method.getAnnotation(PutMapping.class).value());
        } else if (method.isAnnotationPresent(DeleteMapping.class)) {
            return firstValue(method.getAnnotation(DeleteMapping.class).value());
        } else if (method.isAnnotationPresent(RequestMapping.class)) {
            return firstValue(method.getAnnotation(RequestMapping.class).value());
        }
        return "";
    }

    private String firstValue(String[] values) {
        return (values != null && values.length > 0) ? values[0].trim() : "";
    }

    private String combinePath(String classPath, String methodPath) {
        classPath = (classPath == null) ? "" : classPath.trim();
        methodPath = (methodPath == null) ? "" : methodPath.trim();
        if (classPath.isEmpty()) return methodPath;
        if (methodPath.isEmpty()) return classPath;
        return classPath.endsWith("/")
                ? classPath + methodPath.replaceFirst("^/", "")
                : classPath + "/" + methodPath;
    }
}

需要统计可以

// 慢接口异步入库

@Async("slowApiExecutor")

public void asyncSaveSlowApi(String url, String method, long costTime) {

try {

// 这里实现入库逻辑

} catch (Exception e) {

log.error("慢接口入库失败", e);

}

}

@Configuration

@EnableAsync // 开启异步支持

public class AsyncConfig {

@Bean("slowApiExecutor")

public Executor slowApiExecutor() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2); // 核心线程数

executor.setMaxPoolSize(30); // 最大线程数

executor.setQueueCapacity(2000); // 任务队列容量

executor.setThreadNamePrefix("slow-api-"); // 线程前缀

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略

executor.initialize();

return executor;

}

}

相关推荐
the局外人12 分钟前
掌控自己的 K 线数据:TradingView 本地部署与数据源接入
前端·vue.js·websocket
星间都市山脉32 分钟前
Android16 SystemService.onBootPhase 调用时机
android·java·linux·windows·ubuntu
deli00740 分钟前
配色对比度检查器:一眼看清文字和背景谁看不清
前端
梦诺41 分钟前
vue3 keepAlive+记录滚动条
前端·javascript·vue.js
葡萄城技术团队1 小时前
SpreadJS V19.2 新特性揭秘:甘特表的进度线
前端
计算机魔术师1 小时前
黄仁勋台上接特朗普电话开免提,全场听到一句话:AI 不会减速
前端
小羊没烦恼!1 小时前
Memory 记忆设计讨论:为什么 Agent Memory 不能只靠向量数据库?
java·开发语言·windows·算法·c#
yivifu1 小时前
HTML元素的textContent和innerText两个属性的差别
前端·html
黑马程序员毕设2 小时前
基于微信小程序的节目活动报名与管理系统设计与实现
java·开发语言·spring boot·后端·电脑
Bs_MoneyMagnet2 小时前
基于springboot+vue的会议室预约管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计