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

Spring AOP 實戰:AspectJ 註解與 XML 兩種切面配置方式完整指南

     編輯:Java 綜合教學

前言:聲明式 AOP 配置,註解靈活、XML 集中,按場景選型

Spring 支持 @AspectJ 註解風格 和 傳統 XML 風格 兩種切面配置。本文以完整可運行代碼演示兩種方式實現 Before、AfterReturning、After、Around、AfterThrowing 五大通知,並解決 缺少 aopalliance 依賴導致 NoClassDefFoundError 等經典坑點。

一、 環境準備:必備依賴與配置

1.1 Maven 依賴 (Spring 4.2.5 為例)


<dependencies>
    <!-- Spring AOP 核心 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.2.5.RELEASE</version>
    </dependency>
    <!-- AspectJ 運行時織入 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>
    <!-- 關鍵:AOP Alliance 接口,缺失導致 NoClassDefFoundError: MethodInterceptor -->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

1.2 Spring XML 頭部聲明 (啟用 AOP 命名空間)


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
       xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="https://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           https://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           https://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <!-- 配置內容 -->
</beans>

二、 目標對象:Person 接口與 WangYang 實現


public interface Person {
    void say();
    void sayName(String name);
    String saySth(String name);
    void sayEx(); // 拋 NPE 測試 AfterThrowing
}

public class WangYang implements Person {
    public void say() { System.out.println("wy!"); }
    public void sayName(String name) { System.out.println("name-->" + name); }
    public String saySth(String name) { System.out.println("name-->" + name); return name; }
    public void sayEx() { Object obj = null; obj.equals("1"); } // 故意拋 NPE
}

三、 方式一:@AspectJ 註解配置 (推薦,就近維護)

3.1 切面類:MyInterceptor


@Aspect
public class MyInterceptor {

    @Pointcut("execution(* com.example.aop.WangYang.*(..))")
    public void anyMethod() {}

    @Before("anyMethod()")
    public void checkMessage(JoinPoint jp) {
        System.out.println("@Before-check!!!");
        System.out.println("@Before-目標: " + jp.getTarget());
        System.out.println("@Before-方法: " + jp.getSignature().getName());
        System.out.println("@Before-參數: " + Arrays.toString(jp.getArgs()));
    }

    @AfterReturning(pointcut = "anyMethod()", returning = "obj")
    public void checkReturn(JoinPoint jp, Object obj) {
        System.out.println("@AfterReturning-返回值: " + obj);
        System.out.println("@AfterReturning-生成日志");
    }

    @After("anyMethod()")
    public void checkAfter(JoinPoint jp) {
        System.out.println("@After-釋放資源!");
    }

    @Around("anyMethod()")
    public Object checkAround(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("@Around!");
        Object[] args = jp.getArgs();
        if (args != null && args.length > 0 && args[0] instanceof String) {
            args[0] = "@Around" + args[0]; // 修改參數
        }
        Object result = jp.proceed(args); // 執行目標方法
        if (result instanceof String) {
            result = result + "--wy"; // 修改返回值
        }
        return result;
    }

    @AfterThrowing(throwing = "ex", pointcut = "anyMethod()")
    public void checkAfterThrowing(Throwable ex) {
        System.out.println("@AfterThrowing-異常拋出!" + ex.getClass().getSimpleName());
    }
}

3.2 XML 註冊切面與目標 Bean


<beans ...>
    <!-- 開啟 @AspectJ 自動代理 -->
    <aop:aspectj-autoproxy />

    <bean id="wangYang" class="com.example.aop.WangYang" />
    <bean id="myInterceptor" class="com.example.aop.MyInterceptor" />
</beans>

四、 方式二:XML 純配置風格 (集中管理、無侵入)

4.1 普通 POJO 切面類 (無註解)


public class MyInterceptorXml {
    public void anyMethod() {} // 空方法供切點引用

    public void checkMessage(JoinPoint jp) { /* 同註解版 Before */ }
    public void checkReturn(JoinPoint jp, Object obj) { /* 同註解版 AfterReturning */ }
    public void checkAfter(JoinPoint jp) { /* 同註解版 After */ }
    public Object checkAround(ProceedingJoinPoint jp) throws Throwable { /* 同註解版 Around */ }
    public void checkAfterThrowing(Throwable ex) { /* 同註解版 AfterThrowing */ }
}

4.2 XML 定義切面、切點、通知綁定


<beans ...>
    <aop:config>
        <!-- 定義切點表達式 -->
        <aop:pointcut id="anyMethod" expression="execution(* com.example.aopxml.WangYang.*(..))" />

        <!-- 定義切面 -->
        <aop:aspect id="myAspect" ref="myInterceptorXml">
            <aop:before method="checkMessage" pointcut-ref="anyMethod" />
            <aop:after-returning method="checkReturn" pointcut-ref="anyMethod" returning="obj" />
            <aop:after method="checkAfter" pointcut-ref="anyMethod" />
            <aop:around method="checkAround" pointcut-ref="anyMethod" />
            <aop:after-throwing method="checkAfterThrowing" pointcut-ref="anyMethod" throwing="ex" />
        </aop:aspect>
    </aop:config>

    <bean id="wangYang" class="com.example.aopxml.WangYang" />
    <bean id="myInterceptorXml" class="com.example.aopxml.MyInterceptorXml" />
</beans>

五、 測試驗證與執行順序解析

5.1 測試主程序


public class MainTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person wy = ctx.getBean("wangYang", Person.class);

        wy.say();
        wy.sayName("wangyang-");
        String str = wy.saySth("hello--");
        System.out.println("返回值: " + str); // 輸出: @Aroundhello----wy

        // wy.sayEx(); // 取消註釋測試 AfterThrowing
    }
}

5.2 輸出日誌分析 (saySth 調用)

@Around!
@Before-check!!!
@Before-目標: com.example.aop.WangYang@...
@Before-方法: saySth
@Before-參數: [@Aroundhello--]
@AfterReturning-返回值: @Aroundhello----wy
@AfterReturning-生成日志
@After-釋放資源!
返回值: @Aroundhello----wy

5.3 執行順序鐵律

  1. Around 前半部 (proceed 前)
  2. Before
  3. 目標方法執行
  4. AfterReturning (正常返回)
  5. After (finally)
  6. Around 後半部 (proceed 後)
  7. 異常時:AfterThrowing → After → Around 異常傳播

六、 兩種方式選型建議

維度 @AspectJ 註解 XML 配置
代碼侵入性需引入 Spring/AspectJ 註解零侵入,純 POJO
可讀性切面邏輯與定義在一起,就近維護集中在 XML,全局視野好
重構友好IDE 重構自動更新切點表達式字符串表達式重構易漏改
適用場景應用級切面 (日志、事務、緩存、監控)基礎設施級、第三方庫無法加註解、團隊規範統一 XML

七、 常見坑點總結

  1. 缺 aopalliance.jar → NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor,必加。
  2. 切點表達式不匹配:execution(* com.xxx..*.*(..)) 匹配包及子包所有類方法。
  3. Around 必須返回 proceed() 結果:否則調用者拿到 null。
  4. 參數修改需謹慎:args[0] = newVal 修改的是數組引用,基本類型包裝類不可變需重新賦值。
  5. 異常通知 throwing 屬性:必須與方法參數名一致 (throwing="ex" → Throwable ex)。

結語

AspectJ 註解與 XML 雙軌並行,本質都是向 Spring 容器註冊 Advisor (切點 + 通知)。新項目首選 @AspectJ,享受 IDE 支持與就近維護;舊項目遷移或基礎設施層可用 XML 集中治理。掌握五大通知執行順序與參數綁定細節,讓 AOP 真正成為消除樣板代碼的利器,而非調試噩夢。

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