Spring學習(三)| Spring AOP
文章目錄
- 1. 什麼是 AOP ?
- 2. 為什麼需要使用 AOP ?
- 3. AOP 術語
- 4. 在 Spring 中使用 Aspect 註解方式進行切面程式設計
- 5. 指定切面的優先順序
- 6. 重用切入點表示式
- 7. 基於 XML 配置檔案的方式來配置 AOP
1. 什麼是 AOP ?
- AOP:面向切面程式設計,是對OOP(面向物件程式設計)的補充
- AOP 的主要程式設計物件是切面(aspect),,是切面的橫切關注點的模組化
2. 為什麼需要使用 AOP ?
沒有使用 AOP 之前,進行日誌輸出或者驗證的之類需求開發時,會遇到以下問題
- 程式碼混亂:越來越多的非業務需求(日誌和驗證等)加入後, 原有的業務方法急劇膨脹. 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點.
- 程式碼分散: 以日誌需求為例, 只是為了滿足這個單一需求, 就不得不在多個模組(方法)裡多次重複相同的日誌程式碼. 如果日誌需求發生變化, 必須修改所有模組.
AOP 優勢:
- 每個事物邏輯位於一個位置, 程式碼不分散, 便於維護和升級
- 業務模組更簡潔, 只包含核心業務程式碼.
3. AOP 術語
- 切面(Aspect): 橫切關注點(跨越應用程式多個模組的功能)被模組化的特殊物件
- 通知(Advice): 切面必須要完成的工作
- 目標(Target): 被通知的物件
- 代理(Proxy): 向目標物件應用通知之後建立的物件
- 連線點(Joinpoint):程式執行的某個特定位置:如類某個方法呼叫前、呼叫後、方法丟擲異常後等。連線點由兩個資訊確定:方法表示的程式執行點;相對點表示的方位。例如
ArithmethicCalculator#add()
方法執行前的連線點,執行點為ArithmethicCalculator#add()
- 切點(pointcut):每個類都擁有多個連線點:例如 ArithmethicCalculator 的所有方法實際上都是連線點,即連線點是程式類中客觀存在的事務。AOP 通過切點定位到特定的連線點。類比:連線點相當於資料庫中的記錄,切點相當於查詢條件。切點和連線點不是一對一的關係,一個切點匹配多個連線點,切點通過 org.springframework.aop.Pointcut 介面進行描述,它使用類和方法作為連線點的查詢條件。
4. 在 Spring 中使用 Aspect 註解方式進行切面程式設計
4.1 在 Spring 中啟用 AspectJ 註解支援
-
要在 Spring 應用中使用 AspectJ 註解, 必須在 classpath 下包含 AspectJ 類庫
spring-aop-4.3.9.RELEASE.jar
spring-aspects-4.3.9.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver_1.6.10.release.jar -
使用
aop
名稱空間 -
要在 Spring IOC 容器中啟用 AspectJ 註解支援, 只要在 Bean 配置檔案中定義一個空的 XML 元素 <aop:aspectj-autoproxy>
<!-- 使 Aspject 註解起作用:自動為匹配的類生成代理物件 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
當 Spring IOC 容器偵測到 Bean 配置檔案中的
<aop:aspectj-autoproxy>
元素時, 會自動為與 AspectJ 切面匹配的 Bean 建立代理
4.2 用 AspectJ 註解宣告切面
-
把橫切關注點的程式碼抽象到切面的類中
- 切面首先是一個 IOC容器 中的 Bean, 即給切面類加入
@Component
註解 - 切面還需要加入
@Aspect
註解
- 切面首先是一個 IOC容器 中的 Bean, 即給切面類加入
-
在類中宣告各種通知:
通知是標註有某種註解的簡單的 Java 方法(宣告一個方法,在方法前加入如下註解)註釋 作用 @Before 前置通知, 在方法執行之前執行 @After 後置通知, 在方法執行之後執行(無論是否發生異常) @AfterRunning 返回通知, 在方法返回結果之後執行 @AfterThrowing 異常通知, 在方法丟擲異常之後 @Around 環繞通知, 圍繞著方法執行 -
利用方法簽名編寫 AspectJ 切入點表示式
@Before("execution(int com.spring.aop.impl.ArithmeticCalculatorImpl.add(int, int))")
-
可以在通知方法中宣告一個型別為 JoinPoint 的引數. 然後就能訪問連結細節. 如方法名稱和引數值.
/**
* 日誌切面類
*
*/
//把這個類宣告為一個切面,需要把該類放入到 IOC容器中,在宣告為一個切面
@Aspect
@Component
public class LoggingAspect {
//宣告該方法是一個前置通知:在目標方法開始之前執行
@Before("execution(* com.spring.aop.impl.ArithmeticCalculatorImpl.add(int, int))")
public void beforemethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begins with" + args);
}
}
4.3 通知詳解
-
後置通知:後置通知是在連線點完成之後執行的(無論是否發生異常)
- 後置通知因為可能出現異常,所以不能訪問到方法的返回值的
-
返回通知: 如果只想在連線點返回的時候記錄日誌, 應使用返回通知代替後置通知
- 返回通知是可以訪問到方法的返回值的
- 在返回通知中, 只要將 returning 屬性新增到 @AfterReturning 註解中, 就可以訪問連線點的返回值. 該屬性的值即為用來傳入返回值的引數名稱.
- 必須在通知方法的簽名中新增一個同名引數. 在執行時, Spring AOP 會通過這個引數傳遞返回值.
- 原始的切點表示式需要出現在 pointcut 屬性中
-
異常通知:只在連線點丟擲異常時才執行異常通知,可以訪問到方法出現的異常
- 將 throwing 屬性新增到 @AfterThrowing 註解中, 也可以訪問連線點丟擲的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常.
- 如果只對某種特殊的異常型別感興趣, 可以將引數宣告為其他異常的引數型別. 然後通知就只在丟擲這個型別及其子類的異常時才被執行.
-
環繞通知:
5. 指定切面的優先順序
- 在同一個連線點上應用不止一個切面時, 除非明確指定, 否則它們的優先順序是不確定的.
- 切面的優先順序可以通過實現 Ordered 介面或利用 @Order 註解指定.
- 實現 Ordered 介面, getOrder() 方法的返回值越小, 優先順序越高.
- 若使用 @Order 註解, 序號出現在註解中
@Order(0) @Aspect @Component public class LoggingAspect {} ``
6. 重用切入點表示式
-
在編寫 AspectJ 切面時, 可以直接在通知註解中書寫切入點表示式. 但同一個切點表示式可能會在多個通知中重複出現.
-
在 AspectJ 切面中, 可以通過 @Pointcut 註解將一個切入點宣告成簡單的方法. 切入點的方法體通常是空的
/** * 定義一個方法,用於宣告切入點表示式,一般的,該方法不再需要填入其他的程式碼 * */ @Pointcut("execution(int com.spring.aop.impl.ArithmeticCalculatorImpl.add(int, int))") public void dJoinPointExp(){} //呼叫的方法如下 @After("dJoinPointExp()") public void afterMethod() { System.out.println("Method is ending..."); }
7. 基於 XML 配置檔案的方式來配置 AOP
- 使用
aop
名稱空間
Eg:
<!-- 配置需要被切入的 bean -->
<bean id="arithmeticCalculator" class="com.spring.aop.xml.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面的 bean -->
<bean id="loggingAspect" class="com.spring.aop.impl.LoggingAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切點表示式 -->
<aop:pointcut expression="execution(int com.spring.aop.xml.ArithmeticCalculatorImpl.add(int, int))" id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>