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;

}

}

相关推荐
一品人家9 小时前
win32汇编使用GDI+入门教程之九
汇编·windows·win32汇编
华玥作者11 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_12 小时前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
qq_2975746712 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
前端摸鱼匠12 小时前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang2015092812 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC13 小时前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务13 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整14 小时前
面试点(网络层面)
前端·网络
VT.馒头14 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript