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;

}

}

相关推荐
zzzzzz31042 分钟前
react-bits:从 36K Stars 的组件库,看动画交互组件该如何被评估
前端·react.js·动效
Setsuna_F_Seiei8 小时前
前端的 AI 学习之路 01 之 Agent API 调用 - 和 Agent 的基础对话
前端·人工智能·ai编程
threerocks8 小时前
AI 原生软件开发生命周期手册 - 如何借助 AI,逐阶段改造软件开发生命周期
前端·javascript·后端
徐小夕9 小时前
3分钟从想法到Agent上线:我们开源了一款AI可视化工作流“IDE”
前端·算法·github
深漂的华哥9 小时前
Ruoyi-Plus前后端分离场景下,数据加密传输
java·spring boot·后端·开源·maven·ruoyi
why技术11 小时前
eli5,我觉得这个全网在吹的技能,使用体验真的很一般啊。
前端·人工智能·后端
excel11 小时前
记录升级 nuxt3 到 nuxt4 记录
前端
剑胆琴心静水深流11 小时前
全栈之路6---web集成与呈现
前端·vue.js·spring boot·分布式·spring·前端框架·npm
前端snow11 小时前
ai agent -- Memory汇总
前端
爱学习的小白柏12 小时前
【AI问数技术】多Agent协同架构:查询规划/SQL生成/洞察分析/报告生成
java·网络·人工智能·windows·sql·架构·llama