核心基于 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;
}
}