@Aspect
@Component
@Slf4j // Lombok 日誌
public class MethodLatencyAspect {
/** 耗時閾值(毫秒),超過才記錄 WARN,避免日誌淹沒 */
private static final long SLOW_THRESHOLD_MS = 100;
/** 切點:service 包下所有類的所有方法 */
@Pointcut("execution(* com.example.service..*.*(..))")
public void serviceLayer() {}
@Around("serviceLayer()")
public Object measure(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
String methodSignature = getSignature(pjp);
try {
return pjp.proceed();
} finally {
long elapsedMs = (System.nanoTime() - start) / 1_000_000;
if (elapsedMs > SLOW_THRESHOLD_MS) {
log.warn("[SLOW] {} 耗時 {} ms", methodSignature, elapsedMs);
} else if (log.isDebugEnabled()) {
log.debug("[TIMING] {} 耗時 {} ms", methodSignature, elapsedMs);
}
}
}
private String getSignature(ProceedingJoinPoint pjp) {
MethodSignature ms = (MethodSignature) pjp.getSignature();
return ms.getDeclaringTypeName() + "." + ms.getName() + Arrays.toString(ms.getParameterTypes());
}
}
@Around("serviceLayer()")
public Object measureWithDetails(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
String sig = getSignature(pjp);
Object[] args = pjp.getArgs();
String argsJson = safeToJson(args); // 過濾密碼、Token 等敏感字段
log.debug("▶ {} 輸入: {}", sig, argsJson);
try {
Object result = pjp.proceed();
long ms = (System.nanoTime() - start) / 1_000_000;
String retJson = safeToJson(result);
if (ms > SLOW_THRESHOLD_MS) {
log.warn("⏱ {} 耗時 {} ms | 出參: {}", sig, ms, retJson);
} else {
log.debug("⏱ {} 耗時 {} ms | 出參: {}", sig, ms, retJson);
}
return result;
} catch (Throwable ex) {
long ms = (System.nanoTime() - start) / 1_000_000;
log.error("✖ {} 耗時 {} ms 異常: {}", sig, ms, ex.getMessage(), ex);
throw ex;
}
}
private String safeToJson(Object obj) {
try {
// 使用 Jackson 忽略 @JsonIgnore、@JsonProperty(access=WRITE_ONLY) 字段
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
return obj == null ? "null" : obj.getClass().getSimpleName() + "@" + obj.hashCode();
}
}
<!-- applicationContext.xml -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 或 CGLIB 強制代理類 -->
Spring Boot 只需引入 spring-boot-starter-aop,自動啟用 @EnableAspectJAutoProxy。
| 優化點 | 做法 |
|---|---|
| 性能開銷 | 使用 System.nanoTime() 而非 currentTimeMillis();僅 WARN 級別輸出完整參數,DEBUG 級別簡略。 |
| 采樣率 | 高吞吐接口配置 @Around("serviceLayer() && @annotation(Sampled)"),自定義註解控制采樣。 |
| 異步日誌 | Log4j2 AsyncAppender / Logback AsyncAppender,避免日誌 I/O 阻塞業務線程。 |
| 指標收集 | 接入 Micrometer + Prometheus,將耗時作為 Histogram 指標導出,Grafana 告警,而非依賴文本日誌。 |
| 分布式追蹤 | 結合 SkyWalking / Zipkin / OpenTelemetry,自動獲取 Span 耗時,無需手寫切面。 |
@Component)、方法為 private/final/static、類內部調用繞過代理(需注入自身或使用 AopContext.currentProxy())。finally 塊必須拋出原異常,否則事務回滾失效。Spring AOP @Around 切面是方法級耗時監控的「瑞士軍刀」:代碼極簡、零侵入、靈活擴展。生產環境建議 **「切面采集 + 異步日誌 + 指標導出 + 分布式追蹤」** 組合拳,逐步替代手工埋點,實現全鏈路可觀測。