AOP(面向切面編程)是 Spring 核心特性之一,本文通過完整 XML 配置範例,系統講解 5 種通知類型、切點表達式語法、代理機制原理與實戰測試。
| 通知類型 | XML 標籤 | 執行時機 | 能否阻止目標方法 | 能否獲取返回值 | 典型場景 |
|---|---|---|---|---|---|
| 前置通知 | <aop:before> | 目標方法前 | 否 | 否 | 權限檢查、日誌記錄、事務開啟 |
| 後置通知 | <aop:after-returning> | 目標方法正常返回後 | 否 | 是(returning) | 事務提交、結果緩存、审計日誌 |
| 最終通知 | <aop:after> | 目標方法結束時(無論正常/異常) | 否 | 否 | 資源清理、鎖釋放、性能埋點 |
| 異常通知 | <aop:after-throwing> | 目標方法拋出異常時 | 否 | 否(可獲取異常 throwing) | 異常日誌、錯誤通知、補償邏輯 |
| 環繞通知 | <aop:around> | 完全包裹目標方法 | 是(pjp.proceed()) | 是 | 事務管理、緩存攔截、性能監控、重試邏輯 |
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:aop="https://www.springframework.org/schema/aop"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目標 Bean -->
<bean id="personDAO" class="com.lee.spring002.aop.xml.PersonDAOImpl"/>
<!-- 切面 Bean -->
<bean id="transaction" class="com.lee.spring002.aop.xml.Transaction"/>
<aop:config>
<!-- 切點表達式:匹配 PersonDAOImpl 所有公共方法 -->
<aop:pointcut expression="execution(* com.lee.spring002.aop.xml.PersonDAOImpl.*(..))" id="perform"/>
<aop:aspect ref="transaction">
<aop:before method="beginTransaction" pointcut-ref="perform"/>
<aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
<aop:after method="finallyDisplay" pointcut-ref="perform"/>
<aop:after-throwing method="exception" pointcut-ref="perform" throwing="content"/>
<aop:around method="arround" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
</beans>
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
關鍵符號:* 匹配任意字符,.. 匹配任意參數列表或包層級,+ 表示類及其子類(用於類名後)。
public interface IPersonDAO {
String savePerson();
}
public class PersonDAOImpl implements IPersonDAO {
@Override
public String savePerson() {
System.out.println("PersonDAOImpl - savePerson()");
// int a = 1 / 0; // 可取消註釋測試異常通知
return "save successfully";
}
}
public class Transaction {
// 前置通知
public void beginTransaction(JoinPoint jp) {
System.out.println("連接點名稱: " + jp.getSignature().getName());
System.out.println("目標類名稱: " + jp.getTarget().getClass());
System.out.println("Begin transaction...");
}
// 後置通知:獲取返回值
public void commit(JoinPoint jp, Object val) {
System.out.println("Transaction commit...");
System.out.println("returning: " + val.toString());
}
// 最終通知
public void finallyDisplay() {
System.out.println("finally...");
}
// 異常通知:獲取異常對象
public void exception(JoinPoint jp, Throwable content) {
System.out.println("exception: " + content);
System.out.println("exception msg: " + content.getMessage());
}
// 環繞通知:控制目標方法執行
public void arround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("arround before...");
pjp.proceed(); // 必須調用,否則目標方法不執行
System.out.println("arround after...");
}
}
@Test
public void testPerson() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IPersonDAO personDAO = (IPersonDAO) context.getBean("personDAO");
personDAO.savePerson();
}
arround before...
連接點名稱: savePerson
目標類名稱: class com.lee.spring002.aop.xml.PersonDAOImpl
Begin transaction...
PersonDAOImpl - savePerson()
Transaction commit...
returning: save successfully
finally...
arround after...
personDAO、transaction 兩個 Bean<aop:config>,提取切點表達式 performgetBean("personDAO") 時,返回代理對象而非原始 Beanaround(before) → before → 目標方法 → around(after) → after-returning → afterOrdered 或加 @Order 控制優先級pjp.proceed(),否則目標方法不會執行;可多次調用實現重試try-catchreturning="val" 必須與通知方法參數名一致完整 Maven 專案:https://github.com/leechenxiang/maven-spring002-aop