天天看點

深入了解Spring核心容器面向切面概念和實踐:AOP實戰經驗

作者:程式員進階碼農II

AOP實戰經驗

深入了解Spring核心容器面向切面概念和實踐:AOP實戰經驗

介紹完Spring AOP所具備的功能特性,接下來,讓我們看看在應用程式中使用AOP時應該遵循哪些最佳實踐。

活用切點表達式

Spring AOP的一大特色在于為開發人員提供了非常靈活的切點機制。

Spring在編譯期間處理切入點,并嘗試進行優化比對。然而,檢查代碼中的比對規則将是一個代價高昂的過程。

是以,為了獲得最佳性能,我們需要仔細考慮想要實作的目标,并盡可能縮小搜尋或比對條件的範圍。

我們在3.1.2節中已經看到過一個切點表達式,如代碼清單3-21所示。

代碼清單3-21 切點表達式代碼

@Pointcut("execution(*

com.springboot.aop.service.AccountService.doAccountTransaction(..))")

public void doAccountTransaction() {}

這裡的execution()代表的就是表達式的主體,它的基本文法如代碼清單3-22所示,其中“?”部分表示可選項,可以為空。

代碼清單3-22 execution()基本文法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?

name-pattern(param-pattern)throws-pattern?)這個文法看似複雜,但是我們逐個分解所有的模式,它們其實就是描述了一個方法的特征。

modifiers-pattern:表示方法的修飾符。

ret-type-pattern:表示方法的傳回值。

declaring-type-pattern:表示方法所在的類的路徑。

name-pattern:表示方法名。

param-pattern:表示方法的參數。

throws-pattern:表示方法抛出的異常。

這些模式的作用就是完成切點的比對。在各個模式中,可以使用“*”來表示比對所有選項。Spring AOP還為開發人員提供了一組非常有用的描述符來簡化切點表達式的使用過程。例如,args描述符表示方法的參數屬于一個特定的類;within描述符表示方法屬于一個特定的類;target描述符表示方法所屬的類等。關于這些描述符的具體使用方法,可以參考Spring AOP的官方文檔:https://docs.spring.io/springframework/docs/current/reference/html/core.html#spring-core。

為了獲得良好的性能,在設計切點表達式時,至少應該包含方法和類型模式。這并不是說如果隻使用方法或類型模式中的一種,比對就會不生效,而是因為類型模式的比對過程非常快,它通過快速選擇無法進一步處理的連接配接點來縮小搜尋空間。

同時,建議在空方法上聲明切點,并通過空方法名引用這些切點。我們在3.1.2節中定義的doAccountTransaction()方法就是一個很好的空方法。基于這種定義,針對需要對切點表達式進行任何更改的場景,隻需要修改一個位置即可。

另外一項最佳實踐在于盡量聲明小的切點,并把它們組合起來建構複雜的切點。代碼清單3-23展示了定義小切點并将它們連接配接起來的代碼示例。

代碼清單3-23 定義并連接配接小切點代碼示例

@Pointcut("execution(public * *(..))")

private void anyPublicMethod() {}

@Pointcut("execution(*

com.springboot.aop.service.AccountService.doAccountTransaction(..))")

public void doAccountTransaction() {}

@Pointcut("anyPublicMethod() && doAccountTransaction()")

private void transactionOperation() {}

這裡的transactionOperation()就是由anyPublicMethod()和

doAccountTransaction()這兩個切點組合而成的。在日常開發過程中,我們可以根據需要定義各種粒度的切點,并把它們靈活地進行組合。

確定類内方法調用能夠應用代理

請注意,并不是所有場景下Spring AOP都是能夠生效的,例如,在代碼清單3-24所示的ServiceImpl中,直接調用添加了@Transactional注解的handleData()方法時,事務機制并不會生效。

代碼清單3-24 在類内方法上使用代理代碼示例

public class ServiceImpl implements Service {

@Override

public void performBusiness(){

//事務無效

this.handleData();

}

@Transactional

public void handleData() {

}

}

這是因為Spring AOP是通過代理實作的,而無論是JDK代理還是CGLIB代理,其運作機制是對某一個外部的接口或實作類進行代理,像上述代碼中直接調用ServiceImpl類内的方法是不會應用代理的。

解決這一問題的常見方法就是使用上下文對象AopContext,示例代碼如代碼清單3-25所示。

代碼清單3-25 AopContext使用代碼示例

public class ServiceImpl implements Service {

public void performBusiness(){

//從AopContext中擷取代理對象

((Service)AopContext.currentProxy()).handleData();

}

@Transactional

public void handleData() {

}

}

這裡我們直接從AopContext中擷取代理對象。當然,上述代碼生效的前提是確定ProxyFactoryBean的exposeProxy屬性被設定為true,正如我們在前面讨論的那樣。

避免代理機制引起多次初始化過程

不要在已經受Spring管理的Bean類上使用@Configurable注解,否則它将執行雙重初始化,一次是通過Spring容器,一次是通過AOP切面。這是因為,@Configurable這個注解的作用就是告訴Spring在構造函數運作之前将依賴關系注入對象中。

優先使用JDK動态代理

Spring的推薦做法是盡可能使用JDK動态代理而不是CGLIB代理。如果從頭開始建構應用程式,并且不需要建立對第三方API的代理,那麼建議一切以面向接口的方式來驅動整個系統的設計過程。通過合理設計接口,我們可以實作業務的抽象層,進而確定系統的松耦合架構。這時,讓Spring使用基于接口的JDK動态代理機制來建立代理。

Spring AOP面試題分析

面試題1:Spring AOP是基于什麼技術體系來實作的?

答案:Spring AOP的實作依賴代理機制。代理機制在具體實作上一般有兩種方式,一種是靜态代理機制,一種是動态代理機制。Spring AOP基于動态代理模式提供了面向切面機制。動态代理了解和實作起來比較複雜,我們專門通過一節内容對其進行了詳細的闡述。而且動态代理機制的應用非常廣泛,在Dubbo、MyBatis等架構中的應用方式和實作過程值得我們學習和模仿,同樣這也是面試過程中經常會碰到的話題。

面試題2:Spring AOP中提供了哪些類型的通知機制?

答案:Spring AOP的通知機制類型非常豐富,開發人員可以在方法執行之前、之後、前後、傳回以及抛出異常時實作各種自定義的通知邏輯。而且,Spring AOP實作通知的方式很簡單,用一組注解即可,這些注解包括@Before、@After、@Around、@AfterReturning和@AfterThrowing等,分别對應于方法執行的各個階段。

面試題3:Spring AOP使用了哪幾種動态代理機制?性能上哪種更優?

答案:常見的動态代理實作技術包括JDK自帶的代理類、第三方的CGLIB和javassist。在回答該問題時,這三個名詞是一定要點到的。至于具體的細節,可以視面試的進展合理進行展開,包括給出一些自己開發過程中的實踐體會,或者部分核心類的介紹。

在Spring AOP中,采用了上述三種動态代理機制中的兩種,即JDK和CGLIB。從性能上講,JDK動态代理是優于CGLIB的,本章通過一個案例分析給出了這個結論。在面試過程中,可以把案例的設計方法和實作過程做一些展開。

面試題4:如果想要在一個類的内部方法上實作AOP,你有什麼辦法?

答案:關于Spring AOP有一點需要注意,我們隻能在方法的調用過程中嵌入通知機制。這是很重要的一個限制,會導緻對同一個類中的内部方法無法有效地實施動态代理。這時候,我們可以使用Spring AOP提供的AopContext上下文對象來擷取目前的AOP代理。AopContext是一個非常有用的工具類,想要擷取該類,需要確定ProxyFactoryBean的exposeProxy屬性被設定為true。

面試題5:如果想要基于Spring AOP實作對切點的精細化管理,你有什麼

政策?

答案:Spring AOP建立切點的方式是非常靈活的,Spring專門提供了一個execution()配置方法。開發人員可以根據方法的修飾符、傳回值、類路徑、方法名、方法參數以及異常資訊來設定方法調用與切點的比對規則。這是實作精細化管理的一個次元。

第二個精細化管理的次元是設定切點的粒度。我們可以盡量聲明小的切點,并把它們組合起來建構複雜的切點。這樣,切點之間就具備了靈活的可重用性以及組合性。

本文給大家講解的内容是spring核心容器面向切面概念和實踐:AOP實戰經驗

  • 下文給大家講解的是springweb服務建構輕量級Web技術體系:Spring WebMVC