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;

}

}

相关推荐
0思必得02 小时前
[Web自动化] Selenium模拟用户的常见操作
前端·python·selenium·自动化
凡客丶2 小时前
Windows版Miniconda打包环境迁移到内网离线环境【详解】
windows·python
Apifox.2 小时前
测试用例越堆越多?用 Apifox 测试套件让自动化回归更易维护
运维·前端·后端·测试工具·单元测试·自动化·测试用例
小-黯2 小时前
QT编译MySQL驱动教程(Windows/Linux)
windows·qt·mysql
数智工坊2 小时前
【操作系统-处理器调度】
java·linux·服务器·windows·ubuntu
玉梅小洋2 小时前
Chrome设置链接自动跳转新标签页而不是覆盖
前端·chrome
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-考试管理功能布局+交互优化方案
java·数据库·人工智能·spring boot·交互·ddd·tdd
EndingCoder2 小时前
反射和元数据:高级装饰器用法
linux·运维·前端·ubuntu·typescript