@Component
@Aspect
@Slf4j // Lombok 日誌註解
public class ServiceLayerAspect {
/** 切點:攔截 service 包下所有類的所有方法 */
@Pointcut("execution(* com.demo.service..*.*(..))")
private void serviceLayerPointcut() {}
/** 1. 方法執行前:記錄入參 */
@Before("serviceLayerPointcut()")
public void logBefore(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String params = Arrays.stream(args)
.map(this::safeToJson)
.collect(Collectors.joining(", "));
log.info("[AOP-BEFORE] {}.{} | Params: [{}]", className, methodName, params);
}
/** 2. 正常返回:記錄出參 */
@AfterReturning(pointcut = "serviceLayerPointcut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
log.info("[AOP-RETURN] {}.{} | Result: {}", className, methodName, safeToJson(result));
}
/** 3. 異常拋出:記錄異常堆疊 */
@AfterThrowing(pointcut = "serviceLayerPointcut()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
log.error("[AOP-EXCEPTION] {}.{} | Exception: {}", className, methodName, ex.getMessage(), ex);
}
/** 4. 環繞通知:效能監控 + 異常捕獲(最強大、可控制執行流程) */
@Around("serviceLayerPointcut()")
public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
String signature = className + "." + methodName;
try {
Object result = pjp.proceed(); // 執行目標方法
long cost = System.currentTimeMillis() - start;
// 慢查詢告警閾值
if (cost > 500) {
log.warn("[AOP-SLOW] {} | Cost: {} ms", signature, cost);
} else {
log.debug("[AOP-PERF] {} | Cost: {} ms", signature, cost);
}
return result;
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
log.error("[AOP-ERROR] {} | Cost: {} ms | Exception: {}", signature, cost, e.getMessage(), e);
throw e; // 重新拋出,保持原有異常傳播
}
}
/** 安全序列化:防止循環引用、大對象導致日誌膨脹 */
private String safeToJson(Object obj) {
if (obj == null) return "null";
try {
// 建議使用 Jackson ObjectMapper 配置忽略循環引用
return new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.writeValueAsString(obj);
} catch (Exception e) {
return obj.getClass().getSimpleName() + "@" + Integer.toHexString(obj.hashCode());
}
}
}
<!-- 掃描切面組件 -->
<context:component-scan base-package="com.demo" />
<!-- 啟用 @AspectJ 自動代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制 CGLIB 代理
@ComponentScan("com.demo")
public class AopConfig {
// 可選:自定義 PointcutAdvisor、暴露 ProxyFactoryBean 微調
}
<aop:aspectj-autoproxy> 若只在子容器配置,根容器 Bean 不會被代理| 場景 | 技巧 | 代碼示例 |
|---|---|---|
| 排除特定方法 | 切點表達式排除 | execution(* com.demo.service..*.*(..)) && !execution(* *.get*(..)) |
| 獲取方法簽名/註解 | MethodSignature 轉換 | MethodSignature ms = (MethodSignature) pjp.getSignature(); ms.getMethod().getAnnotation(Transactional.class) |
| 異步日誌不阻塞業務 | @Async + 自定義線程池 | log.info() 改為 asyncLogService.record() |
| 敏感字段脫敏 | 參數處理器 + 註解標記 | @SensitiveField 私有字段,切面中反射脫敏後再記錄 |
| 分散式追蹤集成 | TraceId 透傳 | MDC.put("traceId", traceId);日誌格式包含 %X{traceId} |
總結:Spring AOP 是處理橫切關注點的利器。Service 層日誌、異常、效能監控通過單一切面即可實現「零侵入」接入。關鍵在於:切點精準、日誌結構化、異步非阻塞、敏感數據脫敏、分散式追蹤整合。配置時注意雙容器陷阱,選擇合適代理模式,即可構建生產級可觀測體系。