javascript
基于@AspectJ配置Spring AOP之一--转
原文地址:http://tech.it168.com/j/2007-08-30/200708302209432.shtml
概述?
??? 在低版本Spring中定義一個切面是比較麻煩的,需要實現特定的接口,并進行一些較為復雜的配置,低版本Spring AOP的配置是被批評最多的地方。Spring聽取這方面的批評聲音,并下決心徹底改變這一現狀。在Spring2.0中,Spring AOP已經煥然一新,你可以使用@AspectJ注解非常容易的定義一個切面,不需要實現任何的接口。?
Spring2.0采用@AspectJ注解對POJO進行標注,從而定義一個包含切點信息和增強橫切邏輯的切面,Spring 2.0可以將這個切面織入到匹配的目標Bean中。@AspectJ注解使用AspectJ切點表達式語法進行切點定義,可以通過切點函數、運算符、通配符等高級功能進行切點定義,擁有強大的連接點描述能力。在你學習基于@AspectJ的切面技術后,恐怕你就再也沒有興趣使用低版本Spring AOP的實現技術了,畢竟馬落桃花馬前雪,兩者的易用性、便捷性是不可同日而語的。?
著手使用@AspectJ?
??? 我們知道在低版本的Spring AOP中,你必須使用Pointcut和Advice接口描述切點和增強,并用Advisor組合兩者描述一個切面,@AspectJ則采用JDK 5.0的注解技術描述切點和增強類型,而增強的橫切邏輯就在被標注的POJO中定義。?
使用前的準備
??? 在使用@AspectJ之前,首先你得保證你所使用的JDK的版本是5.0及以上版本,否則無法使用注解技術。 Spring在處理@Aspect注解表達式時,需要使用位于<SPRING_HOME>/lib/asm/目錄下asm的類包:asm-2.2.2.jar、asm-commons-2.2.2.jar和asm-util-2.2.2.jar。asm是輕量級的字節碼處理框架,因為Java的反射機制無法獲取入參名,Spring就利用asm處理@AspectJ中所描述的方法入參名。
此外,Spring采用AspectJ提供的@AspectJ注解類庫及相應的解析類庫,它位于<SPRING_HOME>/lib/aspectj目錄下,將目錄下的aspectjrt.jar和aspectjweaver.jar類包加入類路徑中。
一個簡單的例子?
??? 在做好上節中所提到的前置工作后,我們就可以開始編寫一個基于@AspectJ的切面了,首先來看一個簡單的例子,以便對@AspectJ有一個切身的認識。
下面,我們用@AspectJ注解對一個POJO進行標注,將使其成為一個切面類:?
代碼清單 1 PreGreetingAspect:切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect ①通過該注解將PreGreetingAspect標識為一個切面
public class PreGreetingAspect{
@Before("execution(* greetTo(..))") ②定義切點和增強類型
public void beforeGreeting(){③增強的橫切邏輯
System.out.println("How are you");
}
}
???? 我們“驚奇”地發現這個切面沒有實現任何特殊的接口,它只是一個普通的POJO。它特殊的地方在于使用了@AspectJ注解。?
首先,在PreGreetingAspect類定義處,標注了一個@Aspectj注解,第三方處理程序就可以通過類是否擁有@Aspectj注解判斷其是否是一個切面,如①所示。
其次,在beforeGreeting()方法標簽處,標注了@Before注解,并為該注解提供了成員值"execution(* greetTo(..))",如②所示。②處的注解提供了兩個信息:@Before注解表示該增強是前置增強,而成員值通過@ApsectJ切點表達式語法定義切點:即在目標類的greetTo()方法上織入增強,greetTo()方法可以帶任意的入參和任意的返回值。
最后,在③處的beforeGreeting()方法是增強的橫切邏輯,該橫切邏輯在目標方法前調用,我們通過下圖描述這種關系:?
???
?圖 1 切面的信息構成?
??? PreGreetingAspect類通過注解和代碼,將切點、增強類型和增強的橫切邏輯揉合到一個類中,使切面的定義渾然天成。如果在低版本Spring AOP中,你必須同時創建增強類,切點類以及切面類,并使三者聯合表達相同的信息。?
NaiveWaiter是一個Bean,它擁有一個greetTo()的方法,這個方法連接點匹配于上面我們通過@AspectJ所定義的切點,為了方便后續的說明,我們給出NaiveWaiter的代碼:
下面,我們通過org.springframework.aop.aspectj.annotation.AspectJProxyFactory為NaiveWaiter生成織入PreGreetingAspect切面的代理,如代碼清單 2所示:?
代碼清單 2 AspectJProxyTest?
Spring使用AspectJProxyFactory織入基于@AspectJ切面的工作。在①處,設置了目標對象,在②處添加一個切面類,該類必須是帶@AspectJ注解的類,在③處,我們就可以獲取織入切面的代理對象了。?
接下來,我們直接通過代理對象調用greetTo()和serveTo()代碼,它們產生以下的輸出信息:?
How are you ①表示greetTo()方法被成功地織入了切面?
greet to John...?
serving John...?
通過①處的輸出信息我們可以知道代理對象的greetTo()方法已經織入了切面類所定義的增強邏輯了。
通過配置織入@AspectJ切面?
雖然可以通過編程的方式織入切面,但一般情況下,我們還是使用Spring的配置自動完成創建代理織入切面的工作。?
AnnotationAwareAspectJAutoProxyCreator能夠將@AspectJ注解切面的自動織入到目標Bean中。這里,PreGreetingAspect是使用了@AspectJ注解描述的切面類,而NaiveWaiter是匹配切點的目標類。
如果使用基于Schema的aop命名空間進行配置,事情就更簡單了:
首先,在配置文件中引入aop命名空間,如①、②處所示,然后通過aop命名空間的<aop:aspectj-autoproxy />聲明自動為Spring容器中那些匹配@AspectJ切面的Bean創建代理,織入切面。當然,Spring在內部依舊采用AnnotationAwareAspectJAutoProxyCreator進行自動代理的創建工作,但具體實現的細節已經被<aop:aspectj-autoproxy />隱藏起來了。?
<aop:aspectj-autoproxy />有一個proxy-target-class屬性,默認為false,表示使用JDK動態代理織入增強,當配置為<aop:aspectj-autoproxy proxy-target-class="true" />時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設置為false,如果目標類沒有聲明接口,則Spring將自動使用CGLib動態代理。
@AspectJ語法基礎?
@AspectJ使用JDK 5.0注解和正規的AspectJ 5的切點表達式語言描述切面,由于Spring只支持方法的連接點,所以Spring僅支持部分AspectJ的切點語言。在這節時,我們將對AspectJ切點表達式語言進行必要的學習。
切點表達式函數
??? AspectJ 5的切點表達式由關鍵字和操作參數組成,如execution(* greetTo(..))的切點表達式,“execute”為關鍵字,而“* greetTo(..)”為操作參數。在這里,execute代表目標類執行某一方法,而“* greetTo(..)”是描述目標方法的匹配模式串,兩者聯合起來所表示的切點匹配目標類greetTo()方法的連接點。為了描述方便,我們將execution()稱作函數,而將匹配串“* greetTo(..)”稱作函數的入參。?
Spring支持9個@ApsectJ切點表達式函數,它們用不同的方式描述目標類的連接點,根據描述對象的不同,可以將它們大致分為4種類型:?
? 方法切點函數:通過描述目標類方法信息定義連接點;?
? 方法入參切點函數:通過描述目標類方法入參的信息定義連接點;?
? 目標類切點函數:通過描述目標類類型信息定義連接點;?
? 代理類切點函數:通過描述目標類的代理類的信息定義連接點;?
???? 這4種類型的切點函數,通過表 1進行說明:?
??? 表 1 切點函數?
| 類別 | 函數 | 入參 | 說明 |
| 方法切點函數 | execution() | 方法 匹配模式串 | 表示滿足某一匹配模式的所有目標類方法連接點。如execution(* greetTo(..))表示所有目標類中的greetTo()方法。 |
| @annotation() | 方法注 解類名 | 表示標注了特定注解的目標方法連接點。如@annotation(com.baobaotao.anno.NeedTest)表示任何標注了@NeedTest注解的目標類方法。 | |
| 方法入參切點函數 | args() | 類名 | 通過判別目標類方法運行時入參對象的類型定義指定連接點。如args(com.baobaotao.Waiter)表示所有有且僅有一個按類型匹配于Waiter的入參的方法。 |
| @args() | 類型注 解類名 | 通過判別目標方法的運行時入參對象的類是否標注特定注解來指定連接點。如@args(com.baobaotao.Monitorable)表示任何這樣的一個目標方法:它有一個入參且入參對象的類標注@Monitorable注解。 | |
| 目標類切點函數 | within() | 類名匹配串 | 表示特定域下的所有連接點。如within(com.baobaotao.service.*)表示com.baobaotao.service包中的所有連接點,也即包中所有類的所有方法,而within(com.baobaotao.service.*Service)表示在com.baobaotao.service包中,所有以Service結尾的類的所有連接點。 |
| target() | 類名 | 假如目標類按類型匹配于指定類,則目標類的所有連接點匹配這個切點。如通過target(com.baobaotao.Waiter)定義的切點,Waiter、以及Waiter實現類NaiveWaiter中所有連接點都匹配該切點。 | |
| @within() | 類型注解類名 | 假如目標類按類型匹配于某個類A,且類A標注了特定注解,則目標類的所有連接點匹配這個切點。 如@within(com.baobaotao.Monitorable)定義的切點,假如Waiter類標注了@Monitorable注解,則Waiter以及Waiter實現類NaiveWaiter類的所有連接點都匹配。 | |
| @target() | 類型注解類名 | 目標類標注了特定注解,則目標類所有連接點匹配該切點。如@target(com.baobaotao.Monitorable),假如NaiveWaiter標注了@Monitorable,則NaiveWaiter所有連接點匹配切點。 | |
| 代理類切點函數 | this() | 類名 | 代理類按類型匹配于指定類,則被代理的目標類所有連接點匹配切點。這個函數比較難理解,這里暫不舉例,留待后面詳解。 |
?
@AspectJ除上表中所列的函數外,還有call()、initialization()、 preinitialization()、 staticinitialization()、 get()、 set()、handler()、 adviceexecution()、 withincode()、 cflow()、 cflowbelow()、 if()、 @this()以及@withincode()等函數,這些函數在Spring中不能使用,否則會拋出IllegalArgumentException異常。在不特別聲明的情況下,本書中所講@AspectJ函數均指表 1中所列的函數。?
的控制流。After注解類擁有2個成員:?
? value:該成員用于定義切點;?
? argNames:如前所述。
@DeclareParents?
引介增強,相當于IntroductionInterceptor,DeclareParents注解類擁有2個成員:?
? value:該成員用于定義切點,它表示在哪個目標類上添加引介增強;?
? defaultImpl:默認的接口實現類。?
除引介增強外,其它增強都很容易理解,我們將在本文后續內容中統一講述,但引介增強的使用比較特別,因為我們特別在下節中為其準備了一個實例。?
引介增強用法?
請看以下兩個接口及其實現類,如圖 2所示:?
圖 2 Waiter和Seller?
假設我們希望NaiveWaiter能夠同時充當售貨員的角色,即通過切面技術為NaiveWaiter新增Seller接口的實現。我們可以使用@AspectJ的引介增強來實現這一功能。?
代碼清單 3 EnableSellerAspect?
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import com.baobaotao.Seller;
import com.baobaotao.SmartSeller;
@Aspect
public class EnableSellerAspect {
①為NaiveWaiter添加接口實現
@DeclareParents(value="com.baobaotao.NaiveWaiter",
defaultImpl=SmartSeller.class) ② 默認的接口實現類
public Seller seller; ③要實現的目標接口
}
??? 在EnableSellerAspect切面中,我們通過@DeclareParents為NaiveWaiter添加了一個需要實現的Seller接口,并指定其默認實現類為SmartSeller,然后通過切面技術將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實現Seller接口了。?
在Spring配置文件中配置好切面和NaiveWaiter Bean:?
運行以下測試代碼:?
package com.baobaotao.aspectj.basic;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.baobaotao.Seller;
import com.baobaotao.Waiter;
public class DeclaredParentsTest ...{
public static void main(String[] args) ...{
String configPath = "com/baobaotao/aspectj/basic/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
Seller seller = (Seller)waiter; ①可以成功進行強制類型轉換
seller.sell("Beer", "John");
}
}
代碼成功執行,并輸出以下信息:?
NaiveWaiter:greet to John...?
SmartSeller: sell Beer to John...?
可見,NaiveWaiter已經成功地新增了Seller接口的實現。
切點函數詳解?
切點函數是AspectJ表達式語言的核心,是使用@AspectJ進行切面定義的難點,本節我們通過具體實例對切點函數進行深入學習。為了方便講解,我們假設目標類包括以下7個類,這些目標類都位于com.baobaotao.*包中:?
圖 3 Waiter和Seller類圖?
這些類中,除了SmartSeller#showGoods()方法是protected外,其它的方法都是public。?
@annotation()?
@annotation表示標注了某個注解的所有方法。我們通過一個實例說明@annotation()的用法,TestAspect定義了一個后置增強切面,該增強將應用到標注有NeedTest的目標方法中:?
假設NaughtyWaiter#greetTo()方法標注了@NeedTest注解,而NaiveWaiter#greetTo()方法沒有標注@NeedTest注解,如代碼清單 4所示:?
代碼清單 4 標注了NeedTest注解的NaughtyWaiter?
通過Spring配置自動應用切面:
<aop:aspectj-autoproxy />?
<bean id="naiveWaiter" class="com.baobaotao.NaiveWaiter" />?
<bean id="naughtyWaiter" class="com.baobaotao.NaughtyWaiter" />?
<bean class="com.baobaotao.aspectj.fun.TestAspect" />?
運行以下的代碼:?
代碼清單 5 PointcutFunTest:測試代碼?
輸出以下信息:?
NaiveWaiter:greet to John...?
NaughtyWaiter:greet to Tom... ①對應NaughtyWaiter的greetTo()?
needTestFun() executed!?
從以上的信息中,我們可以獲知切面被正確地織入到NaughtyWaiter#greetTo()方法中。?
execution()?
execution()是最常用的切點函數,其語法如下所示:
除了返回類型模式、方法名模式和參數模式外,其它項都是可選的。與其直接講解該方法的使用規則,還不如通過一個個具體的例子進行理解。下面,我們給出各種使用execution()函數實例。?
1)通過方法簽名定義切點?
? execution(public * *(..))?
匹配所有目標類的public方法,但不匹配SmartSeller和protected void showGoods()方法。第一個*代表返回類型,第二個*代表方法名,而..代表任意入參的方法;?
? execution(* *To(..))?
匹配目標類所有以To為后綴的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一個*代表返回類型,而*To代表任意以To為后綴的方法;?
2)通過類定義切點?
? execution(* com.baobaotao.Waiter.*(..))?
匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter類的greetTo()和serveTo()方法。第一個*代表返回任意類型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法;?
? execution(* com.baobaotao.Waiter+.*(..))?
匹配Waiter接口及其所有實現類的方法,它不但匹配NaiveWaiter和NaughtyWaiter類的greetTo()和serveTo()這兩個Waiter接口定義的方法,同時還匹配NaiveWaiter#smile()和NaughtyWaiter#joke()這兩個不在Waiter接口中定義的方法。
3)通過類包定義切點?
在類名模式串中,“.*”表示包下的所有類,而“..*”表示包、子孫包下的所有類。?
? execution(* com.baobaotao.*(..))?
匹配com.baobaotao包下所有類的所有方法;?
? execution(* com.baobaotao..*(..))?
匹配com.baobaotao包、子孫包下所有類的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有類的所有方法都匹配。“..”出現在類名中時,后面必須跟“*”,表示包、子孫包下的所有類;?
? execution(* com..*.*Dao.find*(..))?
匹配包名前綴為com的任何包下類名后綴為Dao的方法,方法名必須以find為前綴。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切點。?
4)通過方法入參定義切點?
切點表達式中方法入參部分比較復雜,可以使用“*”和“ ..”通配符,其中“*”表示任意類型的參數,而“..”表示任意類型參數且參數個數不限。?
? execution(* joke(String,int)))?
匹配joke(String,int)方法,且joke()方法的第一個入參是String,第二個入參是int。它匹配NaughtyWaiter#joke(String,int)方法。如果方法中的入參類型是java.lang包下的類,可以直接使用類名,否則必須使用全限定類名,如joke(java.util.List,int);?
? execution(* joke(String,*)))?
匹配目標類中的joke()方法,該方法第一個入參為String,第二個入參可以是任意類型,如joke(String s1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,double d2,String s3)則不匹配;
? execution(* joke(String,..)))?
匹配目標類中的joke()方法,該方法第一個入參為String,后面可以有任意個入參且入參類型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都匹配。?
? execution(* joke(Object+)))?
匹配目標類中的joke()方法,方法擁有一個入參,且入參是Object類型或該類的子類。 它匹配joke(String s1)和joke(Client c)。如果我們定義的切點是execution(* joke(Object)),則只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。
args()和@args()?
args()函數的入參是類名,@args()函數的入參必須是注解類的類名。雖然args()允許在類名后使用+通配符后綴,但該通配符在此處沒有意義:添加和不添加效果都一樣。?
1)args()?
該函數接受一個類名,表示目標類方法入參對象按類型匹配于指定類時,切點匹配,如下面的例子:
args(com.baobaotao.Waiter)?
表示運行時入參是Waiter類型的方法,它和execution(* *(com.baobaotao.Waiter))區別在于后者是針對類方法的簽名而言的,而前者則針對運行時的入參類型而言。如args(com.baobaotao.Waiter)既匹配于addWaiter(Waiter waiter),也匹配于addNaiveWaiter(NaiveWaiter naiveWaiter),而execution(* *(com.baobaotao.Waiter))只匹配addWaiter(Waiter waiter)方法;實際上,args(com.baobaotao.Waiter)等價于execution(* *(com.baobaotao.Waiter+)),當然也等價于args(com.baobaotao.Waiter+)。
2)@args()?
該函數接受一個注解類的類名,當方法的運行時入參對象標注發指定的注解時,方法匹配切點。這個切點函數的匹配規則不太容易理解,我們通過以下示意圖對此進行詳細講解:?
??????????? 圖 4 @arg(M)匹配示意圖(1)?
??? T0、T1、T2、T3具有如圖所示的繼承關系,假設目標類方法的簽名為fun(T1 t),它的入參為T1,而切面的切點定義為@args(M),T2類標注了@M。當fun(T1 t)傳入對象是T2或T3時,則方法匹配@args(M)所聲明定義的切點;?
再看下面的情況,假設方法簽名是fun(T1 t),入參對于T1,而標注@M的類是T0,當funt(T1 t)傳入T1、T2、T3的實例時,均不匹配切點@args(M)。?
??????????? 圖 5 @arg(M)匹配示意圖(2)?
??? 在類的繼承樹中,①點為方法簽名中入參類型在類繼承樹中的位置,我們稱之為入參類型點,而②為標注了@M注解的類在類繼承樹中位置,我們稱之為注解點。判斷方法在運行時是否匹配@agrs(M)切點,可以根據①點和②點在類繼承樹中的相對位置來判別:?
1) 如果在類繼承樹中注解點②高于入參類型點①,則該目標方法不可能匹配切點@args(M),如圖 5所示;?
2) 如果在類繼承樹中注解點②低于入參類型點①,則注解點所在類及其子孫類作為方法入參時,該方法匹配@args(M)切點,如圖 4所示。?
下面舉一個具體的例子,假設我們定義這樣的切點:@args(com.baobaotao.Monitorable) ,如果NaiveWaiter標注了@Monitorable,則對于WaiterManager#addWaiter(Waiter w)方法來說,如果入參是NaiveWaiter或其子類對象,該方法匹配切點,如果入參是NaughtyWaiter對象,不匹配切點。如果Waiter標注了@Monitorable,但NaiveWaiter未標注@Monitorable,則WaiterManager#addNaiveWaiter(NaiveWaiter w)卻不匹配切點,這是因為注解點(在Waiter)高于入參類型點(NaiveWaiter)。
within()?
??? 通過類匹配模式串聲明切點,within()函數定義的連接點是針對目標類而言,而非針對運行期對象的類型而言,這一點和execetion()是相同的。但和execution()函數不同的是,within()所指定的連接點最小范圍只能是類,而execution()所指定的連接點,可以大到包,小到方法入參。所以從某種意義上說,execution()函數的功能涵蓋了within()函數的功能。within()函數的語法如下所示:?
形如within(com.baobaotao.NaiveWaiter)是within()函數所能表達的最小粒度,如果試圖用within()匹配方法級別的連接點,如within(com.baobaotao.NaiveWaiter.greet*)將會產生解析錯誤。?
下面是一些使用within()函數的實例:?
? within(com.baobaotao.NaiveWaiter)?
匹配目標類NaiveWaiter的所有方法。如果切點調整為within(com.baobaotao.Waiter),則NaiveWaiter和NaughtyWaiter中的所有方法都不匹配,而Waiter本身是接口不可能實例化,所以within(com.baobaotao.Waiter)的聲明是無意義的;?
? within(com.baobaotao.*)?
匹配com.baobaotao包中的所有類,但不包括子孫包,所以com.baobaotao.service包中類的方法不匹配這個切點;?
? within(com.baobaotao..*)?
匹配com.baobaotao包及子孫包中的類,所以com.baobaotao.service、com.baobaotao.dao以及com.baobaotao.service.fourm等包中所有類的方法都匹配這個切點。
@within()和@target()?
除@annotation()和@args()外,還有另外兩個用于注解的切點函數,它們分別是@target()和@within(),和@annotation()及@args()函數一樣,它們也只接受注解類名作為入參。其中@target(M)匹配任意標注了@M的目標類,而@within(M)匹配標注了@M的類及子孫類。?
@target(M)切點的匹配規則如圖 6所示:?
圖 6 @target(M)匹配目標類示意圖?
假設NaiveWaiter標注了@Monitorable,則其子類CuteNaiveWaiter沒有標注@Monitorable,則@target(com.baobaotao.Monitorable)匹配NaiveWaiter類的所有方法,但不匹配CuteNaiveWaiter類的方法。?
@within(M)切點的匹配規則如圖 7所示:?
圖 7 @within(M)匹配目標類示意圖?
假設NaiveWaiter標注了@Monitorable,而其子類CuteNaiveWaiter沒有標注@Monitorable,則@within(com.baobaotao.Monitorable)不但匹配NaiveWaiter類中的所有方法也匹配CuteNaiveWaiter類中的所有方法。
但有一個特別值得注意地方是,如果標注@M注解的是一個接口,則所有實現該接口的類并不匹配@within(M)。假設Waiter標注了@Monitorable注解,但NaiveWaiter、NaughtyWaiter及CuteNaiveWaiter這些接口實現類都沒有標注@Monitorable,則@within(com.baobaotao.Monitorable)和@target(com.baobaotao.Monitorable)都不匹配NaiveWaiter、NaughtyWaiter及CuteNaiveWaiter。這是因為@within()、@target()以及@annotation()都是針對目標類而言,而非針對運行時的引用類型而言,這點區別需要在開發中特別注意。
target()的this()?
??? target()切點函數通過判斷目標類是否按類型匹配指定類決定連接點是否匹配,而this()則通過判斷代理類是否按類型匹配指定類來決定是否和切點匹配。兩者都僅接受類名的入參,雖然類名可以帶“+”通配符,但對于這兩個函數來說,使用與不使用+通配符,效果完全相同。
1)target()?
target(M)表示如果目標類按類型匹配于M,則目標類所有方法匹配切點,我們通過一些例子理解target(M)的匹配規則:?
? target(com.baobaotao.Waiter)?
NaiveWaiter、NaughtyWaiter以及CuteNaiveWaiter的所有方法都匹配切點,包括那些未在Waiter接口中定義的方法,如NaiveWaiter#simle()和NaughtyWaiter#joke()方法。?
? target(com.baobaotao.Waiter+)?
和target(com.baobaotao.Waiter)是等價的。?
2)this()?
根據Spring的官方文檔,this()函數判斷代理對象的類是否按類型匹配于指定類,如果匹配,則代理對象的所有連接點匹配切點。但通過實驗,我們發現實際情況和文檔有出入,如我們聲明一個this(com.baobaotao.NaiveWaiter)的切點,如果不使用CGLib代理,則生成的代理對象是Waiter類型,而非NaiveWaiter類型,這一點可以簡單地通過instanceof操作符進行判斷。但是,我們發現NaiveWaiter中所有的方法還是被織入了增強。?
在一般情況下,使用this()和target()通過定義切點,兩者是等效的:?
1) target(com.baobaotao.Waiter) 等價于this(com.baobaotao.Waiter)?
2) target(com.baobaotao.NaiveWaiter) 等價于 this(com.baobaotao.NaiveWaiter)?
兩者區別體現在通過引介切面產生的代理對象時的具體表現,如果我們通過本文前面的方法為NaiveWaiter引介一個Seller接口的實現,則this(com.baobaotao.Seller)匹配NaiveWaiter代理對象的所有方法,包括NaiverWaiter本身的greetTo()、serverTo()方法以及通過Seller接口引入的sell()方法。而target(com.baobaotao.Seller)不匹配通過引介切面產生的NaiveWaiter代理對象。
下面通過具體的實例來了解這一微妙的區別,EnableSellerAspect是為NaiveWaiter添加Seller接口實現的引介切面:
TestAspect是通過判斷運行期代理對象所屬類型來定義切點的切面:?
代碼清單 6 TestAspect:通過this()指定切點?
在Spring中配置這兩個切面和NaiveWaiter:?
<aop:aspectj-autoproxy/>?
<bean id="naiveWaiter" class="com.baobaotao.NaiveWaiter" />?
<bean class="com.baobaotao.aspectj.fun.EnableSellerAspect"/>?
<bean class="com.baobaotao.aspectj.fun.TestAspect" />?
首先EnableSellerAspect切面為NaiveWaiter引介Seller接口產生一個實現Seller接口的代理對象,TestAspect在判斷出NaiveWaiter這個代理對象實現Seller接口后,就將其切面織入到這個代理對象中,所以最終NaiveWaiter的代理對象其實共織入了兩個切面。?
運行以下的測試代碼:
輸出以下的代碼:?
NaiveWaiter:greet to John... thisTest() executed! NaiveWaiter:serving John... thisTest() executed! SmartSeller: sell Beer to John... thisTest() executed!可見代理對象的3個方法都織入了代碼清單 6通過this()所定義切面。?
小結?
??? 使用@AspectJ定義切面比基于接口定義的切面更加直觀、更加簡潔,成為Spring所推薦的切面定義方式。掌握切點表達式語法和切點函數是學習@AspectJ的重心,我們分別對9個切點函數進行了詳細的講述。切點表達式非常靈活,擁有強大的切點表達能力,你可以使用通配符、切點函數以及切點運算符定義切點。
Spring 2.0在AOP上花費了很大的功夫,相比于低版本的Spring,我們看到了很大的改進。在掌握低版本Spring AOP相關知識的基礎上,你會發現學習Spring 2.0 基于@AspectJ AOP的新知識不會有太多的門檻。?
?
一個簡單的例子?
??? 在做好上節中所提到的前置工作后,我們就可以開始編寫一個基于@AspectJ的切面了,首先來看一個簡單的例子,以便對@AspectJ有一個切身的認識。
下面,我們用@AspectJ注解對一個POJO進行標注,將使其成為一個切面類:?
代碼清單 1 PreGreetingAspect:切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect ①通過該注解將PreGreetingAspect標識為一個切面
public class PreGreetingAspect{
@Before("execution(* greetTo(..))") ②定義切點和增強類型
public void beforeGreeting(){③增強的橫切邏輯
System.out.println("How are you");
}
}
???? 我們“驚奇”地發現這個切面沒有實現任何特殊的接口,它只是一個普通的POJO。它特殊的地方在于使用了@AspectJ注解。?
首先,在PreGreetingAspect類定義處,標注了一個@Aspectj注解,第三方處理程序就可以通過類是否擁有@Aspectj注解判斷其是否是一個切面,如①所示。
其次,在beforeGreeting()方法標簽處,標注了@Before注解,并為該注解提供了成員值"execution(* greetTo(..))",如②所示。②處的注解提供了兩個信息:@Before注解表示該增強是前置增強,而成員值通過@ApsectJ切點表達式語法定義切點:即在目標類的greetTo()方法上織入增強,greetTo()方法可以帶任意的入參和任意的返回值。
最后,在③處的beforeGreeting()方法是增強的橫切邏輯,該橫切邏輯在目標方法前調用,我們通過下圖描述這種關系:?
???
?圖 1 切面的信息構成?
??? PreGreetingAspect類通過注解和代碼,將切點、增強類型和增強的橫切邏輯揉合到一個類中,使切面的定義渾然天成。如果在低版本Spring AOP中,你必須同時創建增強類,切點類以及切面類,并使三者聯合表達相同的信息。?
NaiveWaiter是一個Bean,它擁有一個greetTo()的方法,這個方法連接點匹配于上面我們通過@AspectJ所定義的切點,為了方便后續的說明,我們給出NaiveWaiter的代碼:
下面,我們通過org.springframework.aop.aspectj.annotation.AspectJProxyFactory為NaiveWaiter生成織入PreGreetingAspect切面的代理,如代碼清單 2所示:?
代碼清單 2 AspectJProxyTest?
Spring使用AspectJProxyFactory織入基于@AspectJ切面的工作。在①處,設置了目標對象,在②處添加一個切面類,該類必須是帶@AspectJ注解的類,在③處,我們就可以獲取織入切面的代理對象了。?
接下來,我們直接通過代理對象調用greetTo()和serveTo()代碼,它們產生以下的輸出信息:?
How are you ①表示greetTo()方法被成功地織入了切面?
greet to John...?
serving John...?
通過①處的輸出信息我們可以知道代理對象的greetTo()方法已經織入了切面類所定義的增強邏輯了。
轉載于:https://www.cnblogs.com/davidwang456/p/5533671.html
總結
以上是生活随笔為你收集整理的基于@AspectJ配置Spring AOP之一--转的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Advice for students
- 下一篇: 摘录1