1. 簡介
今天有很多可用的 AOP 庫,這些庫需要能夠回答一系列問題:
- 它是否與我的現有或新應用程序兼容?
- 我可以在哪裏實現 AOP?
- 它與我的應用程序集成需要多快?
- 它的性能開銷是多少?
在本文中,我們將探討如何回答這些問題,並介紹 Spring AOP 和 AspectJ – Java 中最流行的兩個 AOP 框架。
2. AOP 概念
在開始之前,我們先對術語和核心概念進行快速、高層次的複習:
- 方面 (Aspect) – 是一種在應用程序的多個地方分散使用的標準代碼/功能,通常與實際的業務邏輯不同(例如,事務管理)。每個方面都專注於特定的橫切關注功能。
- 連接點 (Joinpoint) – 是程序執行過程中的特定點,例如方法執行、構造函數調用或字段賦值。
- 建議 (Advice) – 方面在特定連接點執行的操作。
- 切入點 (Pointcut) – 是一正則表達式,用於匹配連接點。當任何連接點與切入點匹配時,與該切入點關聯的指定建議就會被執行。
- 織入 (Weaving) – 是將方面與目標對象鏈接以創建被建議的對象的過程。
3. Spring AOP 和 AspectJ
現在,我們來探討 Spring AOP 和 AspectJ 在多個維度上的表現——例如,其能力、目標、織入方式、內部結構、切入點和簡潔性。
3.1. 功能與目標
簡單來説,Spring AOP 和 AspectJ 有不同的目標。
Spring AOP 的目標是為 Spring IoC 提供一個簡單的 AOP 實現,以解決程序員們最常見的問題。 它並非旨在成為一個完整的 AOP 解決方案 – 只能應用於由 Spring 容器管理的 Bean。
另一方面,AspectJ 是原始的 AOP 技術,旨在提供一個完整的 AOP 解決方案。 它更健壯,但也比 Spring AOP 複雜得多。 此外,AspectJ 還可以應用於所有領域對象。
3.2. 編織 (Weaving)
AspectJ 和 Spring AOP 使用不同類型的編織技術,這會影響它們在性能和易用性方面的行為。
AspectJ 利用三種不同的編織類型:
- 編譯時編織 (Compile-time weaving):AspectJ 編譯器將我們的方面代碼和應用程序源代碼作為輸入,並生成編織後的類文件作為輸出。
- 預編譯編織 (Post-compile weaving):也稱為二進制編織。它用於將現有的類文件和 JAR 文件與我們的方面編織在一起。
- 加載時編織 (Load-time weaving):這與前述二進制編織類似,但編織延遲到類加載器將類文件加載到 JVM 之後。
關於 AspectJ 本身的更深入信息,請參閲這篇文章。
由於 AspectJ 使用編譯時和加載時編織,因此 Spring AOP 使用運行時編織。
通過運行時編織,在應用程序執行期間,使用目標對象的代理(使用 JDK 動態代理或 CGLIB 代理,後面會討論)將方面編織到應用程序中。
3.3. 內部結構與應用
Spring AOP 是一種基於代理的 AOP 框架。這意味着為了將方面應用於目標對象,它會創建該對象的代理。這通過以下兩種方式實現:
- JDK 動態代理 – 這是 Spring AOP 的首選方式。只要目標對象實現了任何一個接口,就會使用 JDK 動態代理
- CGLIB 代理 – 如果目標對象未實現任何接口,則可以使用 CGLIB 代理
我們可以從 官方文檔 中瞭解更多關於 Spring AOP 代理機制的信息。
另一方面,AspectJ 在運行時不做任何操作,因為類直接使用 AspectJ 編譯器進行編譯。
因此,與 Spring AOP 不同,它不需要使用任何設計模式。為了將方面織入代碼中,它引入了其編譯器 AspectJ 編譯器 (ajc),通過它,我們編譯程序並運行它,同時提供一個小型(< 100K)的運行時庫。
3.4. Joinpoint
在第 3.3 節中,我們已經展示了 Spring AOP 基於代理模式。因此,它需要子類化目標 Java 類並相應地應用橫切關注點。
但是,這存在一個限制。我們不能將橫切關注點(或方面)應用於“final” 類,因為它們不能被重寫,從而會導致運行時異常。
同樣,對靜態和 final 方法也適用。Spring 方面不能應用於它們,因為它們不能被重寫。因此,由於這些限制,Spring AOP 僅支持方法執行 joinpoint。
然而,AspectJ 在運行時直接將橫切關注點編織到實際代碼中。與 Spring AOP 不同,它不需要子類化目標對象,因此還支持許多其他 joinpoint。以下是支持的 joinpoint 總結:
| Joinpoint | Spring AOP 支持 | AspectJ 支持 |
|---|---|---|
| Method Call | 否 | 是 |
| Method Execution | 是 | 是 |
| Constructor Call | 否 | 是 |
| Constructor Execution | 否 | 是 |
| Static initializer execution | 否 | 是 |
| Object initialization | 否 | 是 |
| Field reference | 否 | 是 |
| Field assignment | 否 | 是 |
| Handler execution | 否 | 是 |
| Advice execution | 否 | 是 |
此外,在 Spring AOP 中,方面不應用於調用的方法在同一類中。
這顯然是因為當我們調用同一類中的方法時,我們並沒有調用 Spring AOP 提供的代理的方法。如果我們需要這種功能,則必須在不同的 bean 中定義一個單獨的方法,或者使用 AspectJ。
3.5. 簡潔性
Spring AOP 由於它不引入構建過程中額外的編譯器或織入器,因此顯而易見地更簡單。它使用運行時織入,因此與我們常規的構建過程無縫集成。儘管看起來很簡單,但它僅與由 Spring 管理的 Bean 才能工作。
然而,要使用 AspectJ,我們需要引入 AspectJ 編譯器 (ajc) 並重新打包所有庫(除非我們切換到預編譯或加載時織入)。
這當然比前者更復雜——因為它引入了 AspectJ Java 工具(包括一個編譯器 (ajc)、一個調試器 (ajdb)、一個文檔生成器 (ajdoc) 和一個程序結構瀏覽器 (ajbrowser)),我們需要將其與我們的 IDE 或構建工具集成。
3.6. 性能
從性能角度來看,編譯時織入比運行時織入快得多。Spring AOP 是基於代理的框架,因此在應用程序啓動時會創建代理對象。此外,每個方面調用的方法數量也更多,從而對性能產生負面影響。
另一方面,AspectJ 在應用程序執行之前將方面織入到主代碼中,因此沒有額外的運行時開銷,與 Spring AOP 不同。
由於這些原因,基準測試 建議 AspectJ 比 Spring AOP 快近 8 到 35 倍。
4. Summary
下表總結了 Spring AOP 和 AspectJ 之間的主要區別:
| Spring AOP | AspectJ |
|---|---|
| 使用純 Java 實現 | 使用 Java 編程語言的擴展實現 |
| 無需單獨編譯過程 | 需要 AspectJ 編譯器 (ajc),除非設置 LTW |
| 僅支持運行時編織 | 不支持運行時編織。支持編譯時、預編譯和加載時編織 |
| 功能較弱 – 僅支持方法級別的編織 | 功能更強大 – 可以編織字段、方法、構造函數、靜態初始化器、最終類/方法等… |
| 只能在 Spring 容器管理的 Bean 上實現 | 可以在所有域對象上實現 |
| 僅支持方法執行點切 | 支持所有點切 |
| 創建目標對象的代理,並在這些代理上應用方面 | 方面在執行應用程序之前直接編織到代碼中 (在運行時之前) |
| 比 AspectJ 慢得多 | 性能更好 |
| 易於學習和應用 | 與 Spring AOP 相比,更復雜 |
5. 選擇合適的框架
如果我們分析本節中提出的所有論點,就會開始理解,一個框架並沒有比另一個框架更好。
簡單來説,選擇主要取決於我們的需求:
- 框架:如果應用程序未使用 Spring 框架,那麼我們沒有選擇使用 Spring AOP 的選擇,因為它無法管理超出 Spring 容器範圍的任何內容。但是,如果我們的應用程序完全使用 Spring 框架構建,那麼我們可以使用 Spring AOP,因為它易於學習和應用
- 靈活性:鑑於有限的 Joinpoint 支持,Spring AOP 不是一個完整的 AOP 解決方案,但它解決了程序員最常見的問題。儘管如果我們想深入挖掘並充分利用 AOP 的能力,並希望獲得來自各種 Joinpoint 的支持,那麼 AspectJ 是選擇
- 性能:如果我們使用有限的 Aspect,那麼性能差異可以忽略不計。但是,在某些情況下,應用程序可能包含超過十萬個 Aspect。我們不會希望在這些情況下使用運行時編織,因此最好選擇 AspectJ。AspectJ 據稱比 Spring AOP 快 8 到 35 倍
- 最佳組合:這兩個框架完全兼容。我們可以在可能的情況下利用 Spring AOP,同時使用 AspectJ 以獲得前者的不支持的 Joinpoint 的支持
6. 結論
本文分析了 Spring AOP 和 AspectJ 在多個關鍵領域。
我們比較了這兩種 AOP 方法,包括其靈活性以及它們與我們應用程序的適配性。