首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring AOP 實戰:Service 層日誌攔截、異常統一處理、執行時間監控完整方案

     編輯:Java 綜合教學

Spring AOP 在 Service 層的實戰應用:日誌、異常、效能監控一站式解決

1. AOP 核心概念速覽

  • AOP (Aspect Oriented Programming):面向切面編程,補充 OOP 的橫向關注點分離能力
  • 核心術語:JoinPoint(連接點)、Pointcut(切點)、Advice(通知/增強)、Aspect(切面)、Weaving(織入)
  • 五種通知類型:@Before、@AfterReturning、@AfterThrowing、@After、@Around

2. 完整切面實現代碼

@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());
        }
    }
}

3. Spring 配置啟用 AOP

XML 配置

<!-- 掃描切面組件 -->
<context:component-scan base-package="com.demo" />

<!-- 啟用 @AspectJ 自動代理 -->
<aop:aspectj-autoproxy proxy-target-class="true" />

Java Config 配置(推薦)

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制 CGLIB 代理
@ComponentScan("com.demo")
public class AopConfig {
    // 可選:自定義 PointcutAdvisor、暴露 ProxyFactoryBean 微調
}

關鍵避坑:Spring MVC 雙容器導致 AOP 失效

  • 現象:Controller 有 AOP 效果,Service 無效
  • 原因:DispatcherServlet 子容器掃描 Controller,根容器掃描 Service。<aop:aspectj-autoproxy> 若只在子容器配置,根容器 Bean 不會被代理
  • 解決:在根容器配置文件 (applicationContext.xml) 中啟用 AOP,或 Java Config 確保 @EnableAspectJAutoProxy 在根配置類上

4. 進階技巧與最佳實踐

場景技巧代碼示例
排除特定方法切點表達式排除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}

5. 效能考量與代理機制選擇

  • JDK 動態代理:基於介面,輕量,目標類必須實現介面
  • CGLIB:基於繼承,無需介面,最終類/方法不可代理,構造函數會被調用兩次
  • proxy-target-class="true":強制 CGLIB,Spring Boot 2.0+ 預設為 true

總結:Spring AOP 是處理橫切關注點的利器。Service 層日誌、異常、效能監控通過單一切面即可實現「零侵入」接入。關鍵在於:切點精準、日誌結構化、異步非阻塞、敏感數據脫敏、分散式追蹤整合。配置時注意雙容器陷阱,選擇合適代理模式,即可構建生產級可觀測體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved