Spring AOP 歷險記(一)


AOP 簡介

在學習 Spring AOP 之前,我們先來了解一下 AOP。我們都聽過面向對象編程(OOP),那麼 AOP 到底是什麼呢?中文意思我想大家應該都是非常的耳熟能詳了,中文翻譯過來就是面向切面編程,當然也有些人會翻譯成面向方面編程,不過還是感覺面向切面編程更好聽點。來看下官方的定義:

AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.

翻譯過來就是,AOP 是一種編程範式,旨在通過分離橫切關注點來增加模塊化。它通過向現有代碼添加其他行爲而不修改現有代碼本身來實現。相反,我們可以分別聲明這個新代碼和這些新行爲。

【注】面向對象編程的基本單元是類(class),而面向切面編程的基本單元是切面(aspect)

Spring AOP 是什麼

看完了上面 AOP 的定義,那麼接下來我們來看下什麼是 Spring AOP 呢?

Spring AOP 在 Spring 應用程序實現了面向切面編程。在 AOP 中,這些切面實現了一些關注點的模塊化,諸如事務管理日誌記錄或跨越多種類型和對象的安全性(通常稱爲橫切關注點)。

Spring AOP 的主要術語

大多數的技術都會形成自己的一套術語,Spring AOP 也不例外。而 Spring AOP 最主要的三個術語分別是通知(Advice)切點(Pointcut)連接點(Join point)。如圖,展示了這三者是如何聯繫到一塊的。
在這裏插入圖片描述

  • 建言(Advice)    通知定義了切面是什麼以及何時使用,是在方法執行之前或之後要採取的實際操作。Spring 的面向切面編程框架在程序執行期間調用的時間代碼段。

  • 切點(Pointcut)    切點是一個與連接點相匹配的表達式,用於確定是否需要執行建言(Advice)。切點有助於確定切面所通知的連接點的範圍,它的定義會匹配通知所要織入的一個或多個連接點。切點使用與連接點匹配的不同類型的表達式,在 Spring 框架中使用 Aspect 切點表達式語言(SpEL)。

  • 連接點(Join Point)    連接點在應用程序中是具體的點,例如方法執行,異常處理,改變對象的變量值等。在 Spring AOP 中一個連接點總是一個方法的執行點,即只有方法執行點。

Spring AOP 其他常用的術語

  • 切面(Aspect)    一個切面是跨越多個類的橫切關注點的模塊化,比如事務管理。切面可以通過 XML 的方式來配置,也可以通過 Spring AspectJ 集成使用 @Aspect 註解來注入切面。切面是通知(Advice)和切點(Pointcut)的結合。通知和切點共同定義了切面餓的全部內容——它是什麼,在何時和何處完成了其功能。
  • Introductions    introduction 允許向現有的類添加新的方法或屬性。可以將新的方法和實例變量引入現有類而無需改變它們,爲它們提供新的狀態和行爲。Spring AOP 允許我們爲目標對象引入新的接口和對應的實現。
  • Target Object    織入 advice 的目標對象,目標對象也被稱爲 Target object。Spring AOP 是使用運行時代理實現切面的,因此該對象始終是代理對象,指的不是原來的類,而是織入 advice 後所產生的代理類。這意味着在運行時創建子類,其中,覆蓋目標方法並根據配置包含建言(advice)。
  • Weaving    將 aspect 和其他對象連接起來, 並創建代理對象(adviced object)的過程。這可以在編譯時,加載時或在運行時完成,而 Spring AOP 是在運行時執行編織。

織入(weaving)可以在目標對象的幾個不同的生命週期執行:

  • 編譯期織入(Compile Time):編譯目標類時會織入切面,同時,要求要有一個特殊的編譯器。
  • 類裝載期織入(Classload Time):當目標類加載到 JVM 中時會織入切面,同時,要求要有一個特殊的類裝載器。
  • 運行期織入(Runtime):在應用程序執行期間的某些時間點織入切面。AOP 容器將動態生成代理對象,該代理對象將在織入切面時委託給目標對象。

【櫻木天亥注】AspectJ 織入編譯器屬於編譯期織入;AspectJ 5 的裝載期織入(LTW,load-time weaving)屬於類裝載期織入;Spring AOP 織入切面則屬於運行期織入。

Spring AOP 的優點

1、AOP 是非侵入性的

  • 允許開發人員更加專注於業務邏輯的開發,而不是受困於橫切關注點的問題
  • 類可以通過切面獲得建言而無需將 Spring AOP 相關類或接口添加到類中

2、AOP 是通過純 Java 語言實現的

  • 這意味着我們不需要特殊的編譯單元或特殊的類加載器

3、通過 Spring IOC 容器實現依賴注入

4、能夠將多個橫切關注點織入類中而無需調用這些類的橫切關注點

5、能夠集中或模塊化橫切關注點,使得維持和改變這些切點變得容易維護

6、提供了 XML 或 @AspectJ 註解的多種靈活的方式來創建切面

7、易於配置

Spring AOP 的缺點

1、使用基於代理的 AOP,所以僅支持方法級別的建言,不支持屬性級別的建言

2、當且僅當方法的可見性爲 public 時,才支持建言(Advice)

  • 方法可見性爲 private,protected 或者 default 時不支持建言

3、一個切面不能作爲另一個切面的 advice target 。

  • 如果一個類被 @Aspect 標註, 則這個類就不能是其他切面的目標對象了, 因爲使用 @Aspect 後, 這個類就會被排除在 auto-proxying 機制之外。

4、由於性能問題,建言不適用於細粒度的對象,只適合粗粒度的對象。

建言(advice)的類型

建言(Advice)的類型有一下五種。

  • Before advice 在連接點之前執行,但是不能阻止執行流程進入連接點,即不能人爲地在 before advice 代碼中阻止 join point 中代碼的執行。
  • After returning advice 在連接點正常執行完成後執行,例如,一個方法執行完成不拋異常則執行這個 advice
  • After throwing advice 當一個連接點(join point)拋出異常後退出,則執行該 advice
  • After advice 無論連接點是正常退出還是拋出了異常退出,都會執行這個 advice
  • Around advice 圍繞連接點的建言,比如方法調用。這是最常用的 advice,在 join point 執行前和 join point 執行後都執行的 advice。它還負責選擇是否繼續加入連接點還是返回自身的返回值,或者拋出異常來加速方法的執行。

AOP 代理(proxy)

我們都知道,Spring AOP 底層主要有兩種實現,一種是 JDK 動態代理,一種是 CGLIB 動態代理。而 Spring AOP 代理的實現是通過 JDK 動態代理來創建一個具有目標類和通知調用的 Proxy 類,這些類被稱爲 AOP 代理類。

那麼,JDK 動態代理和 CGLIB 動態代理最大不同是什麼呢?前者的原理是 JDK 反射,並且只支持 Java 接口的代理;後者的原理是繼承(extend)與覆寫(override),因此能支持普通的 Java 類的代理。兩種方式都是動態代理,即運行時實時生成代理。但是,由於JVM 的限制,CGLIB 無法替換被代理類已經被載入的字節碼,只能生成並載入一個新的子類作爲代理類,被代理類的字節碼依然存在於JVM 中。

Spring AOP 示例

兩種聲明方式:

1、XML 方式

<!-- 使用 XML 方式,啓用 AspectJ 風格的 Spring AOP -->
<aop:aspectj-autoproxy />

2、Java 配置的方式

//使用 Java 配置的方式,啓用 AspectJ 風格的 Spring AOP
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

定義切面(Aspect)

@Component
@Aspect
public class EmployeeAspect {
	@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBefore(JoinPoint joinPoint) {
        ……
    }
}

【櫻木天亥注】僅僅使用 @Aspect 註解, 並不能將一個 Java 對象轉換爲 Bean, 因此我們還需要使用類似 @Component 之類的註解。

切點表達式

@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBeforeAdvice(JoinPoint joinPoint) {
        ……
    }

【櫻木天亥注】
1、execution(* EmployeeManager.getEmployeeById(…)) 這個切點表達式表示這個切點將會匹配 EmployeeManager 類下面所有的 getEmployeeById 方法,而不管它的參數類型和數量是什麼。
2、@Before 表示當前的 Advice 是在連接點(Join point)之前執行。

連接點(Join point)

//@Component
public class EmployeeManager
{
    public EmployeeDTO getEmployeeById(Integer employeeId) {
        System.out.println("Method getEmployeeById() called");
        return new EmployeeDTO();
    }
}

【櫻木天亥注】如果前面定義切面(Aspect)時沒有添加 @Component 註解,則這裏應該要加上,不然可能不能將一個 Java 對象轉換爲 Bean。

聲明建言(Advice)

Advice 與切點表達式緊密關聯,會在匹配的連接點執行前,執行後或者周圍(around)執行。切點表達式既可以是簡單的一個 pointcut 名字的引用, 又可以是完整的 pointcut 表達式。這裏簡單的舉幾個例子說明如下。

Around advice

@Around("execution(* EmployeeManager.getEmployeeById(..))")
	public Object logAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
		……
	}

Before advice

@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBeforeAdvice(JoinPoint joinPoint) {
        ……
    }


參考來源
1、Introduction to Spring AOP
2、Spring AOP Tutorial Example
3、Spring AOP Example Tutorial – Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration
4、Overview of Spring Aspect Oriented Programming (AOP)
5、Spring AOP 是什麼?
6、徹底征服 Spring AOP 之 理論篇
7、《Spring實戰(第 4 版)》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章