1. 程式人生 > >Spring學習(三)| Spring AOP

Spring學習(三)| Spring 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註解
  • 在類中宣告各種通知:
    通知是標註有某種註解的簡單的 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>