面向切面編程(Aspect Oriented Programming, AOP)通過提供另一種思考程序結(jié)構(gòu)的方式來補充面向?qū)ο缶幊蹋∣OP)。OOP 中模塊化的關(guān)鍵單元是類,而在 AOP 中,模塊化單元是切面。
AOP 旨在從業(yè)務(wù)邏輯中分離出來通用邏輯,切面實現(xiàn)了跨越多種類型和對象的關(guān)注點(例如事務(wù)管理、日志記錄、權(quán)限控制)的模塊化。(這些在 AOP 文獻中通常被稱為 “橫切” 問題。)
切面織入的三種方法:
- 編譯期織入 (Compile Time Weaving,CTW):指在 Java 編譯期,采用特殊的編譯器,將切面織入到 Java 類中。
- 類加載期織入(Load Time Weaving,LTW):指通過特殊的類加載器,在類字節(jié)碼加載到 JVM 時,織入切面。
- 運行期織入:指采用 CGLIB 工具或 JDK 動態(tài)代理進行切面的織入。
AOP 的概念和術(shù)語
讓我們首先定義一些重要的 AOP 概念和術(shù)語。這些術(shù)語不是特定于Spring的。不幸的是,AOP 術(shù)語不是特別直觀。但是,如果 Spring 使用自己的術(shù)語,那將更加令人困惑。
-
切面(Aspect): 跨越多個類的關(guān)注點的模塊化。事務(wù)管理是企業(yè) Java 應(yīng)用程序中橫切關(guān)注點的一個很好的例子。在 Spring AOP 中,切面是通過使用常規(guī)類(基于模式的方法)或使用
@Aspect注釋的常規(guī)類來實現(xiàn)的 。 - 連接點(JoinPoint):程序執(zhí)行期間的一個點,例如執(zhí)行方法或處理異常。在 Spring AOP 中,連接點始終表示方法執(zhí)行。
-
通知(Advice):特定連接點的某個切面采取的操作。不同類型的建議包括
around,before和after通知。許多 AOP 框架(包括 Spring)將通知建模為一個攔截器,并在圍繞著連接點維護了一個攔截器鏈。 - 切入點(Pointcut):匹配連接點的謂詞。建議與切入點表達式相關(guān)聯(lián),并在切入點匹配的任何連接點處運行(例如,執(zhí)行具有特定名稱的方法)。由切入點表達式匹配的連接點的概念是 AOP 的核心,Spring 默認使用 AspectJ 切入點表達式語言。
-
引介增強(Introduction):在一種類型代表上聲明額外的方法或字段。Spring AOP 允許您向任何建議的對象引入新接口(以及相應(yīng)的實現(xiàn))。例如,您可以使用引介增強使 bean 實現(xiàn)
IsModified接口,以簡化緩存。 -
目標對象(Target Object):由一個或多個切面增強的對象。也稱為
advised object。由于 Spring AOP 是使用運行時代理實現(xiàn)的,因此該對象始終是一個被代理對象。 - AOP Proxy:由 AOP 框架創(chuàng)建的對象,用于實現(xiàn)且切面契約(建議方法執(zhí)行等)。在 Spring Framework 中,AOP 代理是 JDK 動態(tài)代理或 CGLIB 代理。
-
織入(Weaving):將切面與其他應(yīng)用程序類型或?qū)ο箧溄右詣?chuàng)建
advised object。這可以在編譯時(例如,使用 AspectJ 編譯器),加載期或在運行期完成。與其他純 Java AOP 框架一樣,Spring AOP 在運行時執(zhí)行編織。
Spring AOP 包括以下類型的通知:
- 前置通知(Before advice):在連接點之前運行但無法阻止執(zhí)行流程進入連接點的增強(除非它拋出異常)。
- 后置通知(After returning advice):在連接點正常完成后運行的增強(例如,如果方法返回而不拋出異常)。
- 異常通知(After throwing advice):如果方法通過拋出異常退出,則執(zhí)行建議。
- 最終通知(After (finally) advice):無論連接點退出的方式(正?;虍惓7祷兀家獔?zhí)行建議。
- 環(huán)繞通知(Around advice):圍繞連接點的增強,例如方法調(diào)用。這是最強大的建議。Around 通知可以在方法調(diào)用之前和之后執(zhí)行自定義行為。它還負責選擇是繼續(xù)執(zhí)行連接點還是通過返回自己的返回值或拋出異常來終止被增強方法的執(zhí)行。
環(huán)繞通知是最通用的通知。由于 Spring AOP(如 AspectJ)提供了全方位的通知類型,因此我們建議您使用可以實現(xiàn)所需行為的影響范圍最小的通知類型。
例如,如果您只需要使用方法的返回值更新緩存,那么最好實現(xiàn)后置通知而不是環(huán)繞通知,盡管環(huán)繞通知可以完成同樣的事情。
使用最具體的建議類型可以提供更簡單的編程模型,減少錯誤的可能性。例如,您不需要在用于環(huán)繞通知的 JoinPoint 上調(diào)用 proceed() 方法,因此,您不會忘記調(diào)用它。
所有通知參數(shù)都是靜態(tài)類型的,因此您可以使用相應(yīng)類型的通知參數(shù)(例如,方法執(zhí)行的返回值的類型)而不是 Object 數(shù)組。
由切入點匹配的連接點的概念是 AOP 的關(guān)鍵,它將其與僅提供攔截的舊技術(shù)區(qū)分開來。切入點使得通知可以獨立于面向?qū)ο蟮膶哟谓Y(jié)構(gòu)進行定向。例如,您可以將一個提供聲明性事務(wù)管理的通知應(yīng)用于跨多個對象的一組方法(例如服務(wù)層中的所有業(yè)務(wù)操作)。
Spring AOP 的功能和目標
Spring AOP 是用純 Java 實現(xiàn)的。不需要特殊的編譯過程。Spring AOP 不需要控制類加載器層次結(jié)構(gòu),因此適合在 servlet 容器或應(yīng)用程序服務(wù)器中使用。
Spring AOP 目前僅支持方法執(zhí)行連接點(建議在 Spring bean 上執(zhí)行方法)。雖然可以在不破壞核心 Spring AOP API 的情況下添加對字段攔截的支持,但未實現(xiàn)字段攔截。如果您需要建議字段訪問和更新連接點,請考慮使用 AspectJ 等語言。
Spring AOP 的 AOP 方法與大多數(shù)其他 AOP 框架的方法不同。目的不是提供最完整的 AOP 實現(xiàn)(盡管 Spring AOP 非常強大)。相反,目標是在 AOP 實現(xiàn)和 Spring IoC 之間提供緊密集成,以幫助解決企業(yè)應(yīng)用程序中的常見問題。
因此,例如,Spring Framework 的 AOP 功能通常與 Spring IoC 容器一起使用。通過使用普通 bean 定義語法來配置切面。這是與其他 AOP 實現(xiàn)的重要區(qū)別。
AOP 代理
Spring AOP 默認使用 AOP 代理的標準 JDK 動態(tài)代理。這使得任何接口(或接口集)都可以被代理。
Spring AOP 也可以使用 CGLIB 代理。這是代理類而不是接口所必需的。默認情況下,如果業(yè)務(wù)對象未實現(xiàn)接口,則使用 CGLIB。
由于優(yōu)化的做法是編程接口而不是類,業(yè)務(wù)類通常實現(xiàn)一個或多個業(yè)務(wù)接口。可以強制使用 CGLIB,在那些需要建議未在接口上聲明的方法或需要將代理對象作為具體類型傳遞給方法的情況下(希望很少見)。
掌握 Spring AOP 是基于代理的這一事實非常重要。
代理機制
Spring AOP 使用 JDK 動態(tài)代理或 CGLIB 為給定目標對象創(chuàng)建代理。JDK 動態(tài)代理內(nèi)置在 JDK 中,而 CGLIB 是一個常見的開源庫。
如果要代理的目標對象實現(xiàn)至少一個接口,則使用 JDK 動態(tài)代理。目標類型實現(xiàn)的所有接口都是代理的。如果目標對象未實現(xiàn)任何接口,則會創(chuàng)建 CGLIB 代理。
如果要強制使用 CGLIB 代理(例如,代理為目標對象定義的每個方法,而不僅僅是那些由其接口實現(xiàn)的方法),您可以這樣做。但是,您應(yīng)該考慮以下問題:
- 使用 CGLIB 時,final 方法無法被增強,因為它們無法在運行時生成的子類中重寫。
- 從 Spring 4.0 開始,代理對象的構(gòu)造函數(shù)不再被調(diào)用兩次,因為 CGLIB 代理實例是通過 Objenesis 創(chuàng)建的。只有當您的 JVM 不允許構(gòu)造函數(shù)繞過時,您才會看到 Spring 的 AOP 支持中的雙重調(diào)用和相應(yīng)的調(diào)試日志條目。
要強制使用 CGLIB 代理,請將元素 proxy-target-class 屬性的值設(shè)置<aop:config> 為 true,如下所示:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
要在使用 @AspectJ 自動代理支持時強制 CGLIB 代理,請將元素的 proxy-target-class 屬性設(shè)置 <aop:aspectj-autoproxy> 為 true,如下所示:
<aop:aspectj-autoproxy proxy-target-class="true"/>
參考文獻
(正文完)