Spring 支持 @AspectJ 註解風格 和 傳統 XML 風格 兩種切面配置。本文以完整可運行代碼演示兩種方式實現 Before、AfterReturning、After、Around、AfterThrowing 五大通知,並解決 缺少 aopalliance 依賴導致 NoClassDefFoundError 等經典坑點。
<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>
<?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>
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
}
@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());
}
}
<beans ...>
<!-- 開啟 @AspectJ 自動代理 -->
<aop:aspectj-autoproxy />
<bean id="wangYang" class="com.example.aop.WangYang" />
<bean id="myInterceptor" class="com.example.aop.MyInterceptor" />
</beans>
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 */ }
}
<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>
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
}
}
@Around! @Before-check!!! @Before-目標: com.example.aop.WangYang@... @Before-方法: saySth @Before-參數: [@Aroundhello--] @AfterReturning-返回值: @Aroundhello----wy @AfterReturning-生成日志 @After-釋放資源! 返回值: @Aroundhello----wy
| 維度 | @AspectJ 註解 | XML 配置 |
|---|---|---|
| 代碼侵入性 | 需引入 Spring/AspectJ 註解 | 零侵入,純 POJO |
| 可讀性 | 切面邏輯與定義在一起,就近維護 | 集中在 XML,全局視野好 |
| 重構友好 | IDE 重構自動更新切點表達式 | 字符串表達式重構易漏改 |
| 適用場景 | 應用級切面 (日志、事務、緩存、監控) | 基礎設施級、第三方庫無法加註解、團隊規範統一 XML |
NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor,必加。execution(* com.xxx..*.*(..)) 匹配包及子包所有類方法。args[0] = newVal 修改的是數組引用,基本類型包裝類不可變需重新賦值。throwing="ex" → Throwable ex)。AspectJ 註解與 XML 雙軌並行,本質都是向 Spring 容器註冊 Advisor (切點 + 通知)。新項目首選 @AspectJ,享受 IDE 支持與就近維護;舊項目遷移或基礎設施層可用 XML 集中治理。掌握五大通知執行順序與參數綁定細節,讓 AOP 真正成為消除樣板代碼的利器,而非調試噩夢。