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;

}

}

相关推荐
Csvn2 小时前
OpenSpec 详细使用教程
前端
之歆3 小时前
Day19_LESS 完全指南——从入门到工程实践
前端·css·less
云水一下4 小时前
HTML5 从入门到精通:实战收官——从零搭建完整静态网站,综合运用所有知识
前端·html5
不总是4 小时前
Windows 系统 Node.js 免安装版(zip)安装与配置教程(2026 最新)
前端·windows·node.js
冬奇Lab4 小时前
每日一个开源项目(第105篇):Twenty - 跳出 Salesforce 的圈套,定义现代开源 CRM
前端·后端·开源
zhangyao9403305 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
kjs--5 小时前
浏览器书签执行脚本
前端
之歆5 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化
沄媪5 小时前
CSRF 跨站请求伪造
前端·ctf·csrf
kyriewen6 小时前
我关掉了Copilot:因为我写的代码出现在了别人的建议里
前端·javascript·ai编程