Spring提供了4種類型的AOP支持:
- 基于代理的經(jīng)典Spring AOP
- 純POJO切面
- @AspectJ注解驅(qū)動(dòng)的切面
- 注入式AspectJ切面(適用于Spring各版本)
Spring僅支持AspectJ切點(diǎn)指示器(pointcut designator)的一個(gè)子集:
AspectJ指示器 | 描述 |
---|---|
arg() | 限制連接點(diǎn)匹配參數(shù)為指定類型的執(zhí)行方法 |
@args() | 限制連接點(diǎn)匹配參數(shù)由指定注解標(biāo)注的執(zhí)行方法 |
execution() | 用于匹配是連接點(diǎn)的執(zhí)行方法 |
this() | 限制連接點(diǎn)匹配AOP代理的bean引用為指定類型的類 |
target | 限制連接點(diǎn)匹配目標(biāo)對象為指定類型的類 |
@target() | 限制連接點(diǎn)匹配特定的執(zhí)行對象,這些對象對應(yīng)的類要具有指定類型的注解 |
within() | 限制連接點(diǎn)匹配指定的類型 |
@within() | 限制連接點(diǎn)匹配指定注解所標(biāo)注的類型(當(dāng)時(shí)用Spring AOP時(shí)结闸,方法定義在由指定的注解所標(biāo)注的類里) |
@annotation | 限定匹配帶有指定注解的連接點(diǎn) |
在Spring中使用AspectJ其他指示器時(shí)癣蟋,將會(huì)拋出IllegalArgumentException異常。
切點(diǎn)表達(dá)式(以劇院為例子):
execution(* concert.Performance.perform(..))
*:返回任意類型
concert.Performance
:方法所屬的類
.perform(..):方法
僅匹配concert包:
execution(* concert.Performance.perform(..)) && within(concert.*)
可使用關(guān)系符(如&&, ||等)連接够傍, "!"標(biāo)識(shí)not操作濒生。
除上表所列的指示器佩耳,Spring還引入了一個(gè)新的bean()指示器,它允許我們在切點(diǎn)表達(dá)式中使用bean的id來標(biāo)識(shí)bean:
execution(* concert.Performance.perform(..)) && bean('xiyangyang')
指定對id為'xiyangyang'的bean進(jìn)行操作真椿;
execution(* concert.Performance.perform(..)) && 鹃答!bean('xiyangyang')
對所有id不為'xiyangyang'的bean操作。
使用注解創(chuàng)建切面
Spring的AspectJ自動(dòng)代理僅僅使用@AspectJ作為創(chuàng)建切面的指導(dǎo)突硝,切面依然是基于代理的测摔。
創(chuàng)建環(huán)繞通知
環(huán)繞通知(@Around)是最為強(qiáng)大的通知類型,它能使你編寫的邏輯將被通知的目標(biāo)方法完全包裝起來,就像在一個(gè)通知方法中同時(shí)編寫前置通知和后置通知锋八。
例:
@Aspect
public class Audience{
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){}
@Around("performance()") //環(huán)繞通知方法
public void watchPerformance(ProceedingJoinPoint jp){
try{
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!");
}catch(Thorwable e){
System.out.println("Demanding a refund");
}
}
}
As we can see,@Around 接受ProceedingJoinPoint作為參數(shù)浙于。這個(gè)對象是必須要有的,我們需要在通知中通過它調(diào)用被通知的方法挟纱。通知方法中可以做任何的事情路媚,當(dāng)要將控制權(quán)交給被通知的方法時(shí),他需要調(diào)用ProceedingJointPoint的proceed()方法樊销。這個(gè)方法必須被調(diào)用整慎,否則通知會(huì)阻塞對被通知方法的調(diào)用。
處理通知中的參數(shù)
假如我們想記錄某方法執(zhí)行的次數(shù)围苫,有兩種方法裤园,一是直接在每次調(diào)用時(shí)記錄使用次數(shù),然而記錄使用次數(shù)和方法本身是不同的關(guān)注點(diǎn)剂府,因此不應(yīng)該屬于方法拧揽。二就是使用切面。
例:
@Aspect
public class TrackCounter{
private Map<Integer, Integer> trackCounts =
new HashMap<Integer, Integer>();
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int)) " + //通知playTrack()方法
"&& args(trackNumber)")
public void trackPlayed(int trackNumber){ }
@Before("trackPlayed(trackNumber)") //播放前腺占,為該磁道計(jì)數(shù)
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount +1);
}
public int getPlayCount(int trackNumber){
return trackCounts.contaisKey(trackNumber)
? trackCounts.get(trackNumber) : 0;
}
}
其中,
"execution(* soundsystem.CompactDisc.playTrack(int)) " + "&& args(trackNumber)"
*
: 返回任意類型
soundsystem.CompactDisc
:方法所屬的類型
.playTrack
:方法
int
:接受int類型的參數(shù)
args(trackNumber)
:指定參數(shù)
這里需要關(guān)注的是args(trackNumber)
限定符淤袜。它表明傳給playTrack()
方法的int類型參數(shù)也會(huì)傳遞到通知中去。參數(shù)的名稱trackNumber
也與竊電方法簽名中的參數(shù)相匹配衰伯。
現(xiàn)在把TrackCounter和要記錄的類定義為bean铡羡,并啟動(dòng)AspectJ代理,就可以記錄播放次數(shù)了意鲸。
知道如何使用切面包裝方法后烦周,可以看看如何通過編寫切面,為被通知的對象引入全新的功能怎顾。
通過注解引入新功能
如果使用代理暴露新接口读慎,切面所通知的bean看起來像是實(shí)現(xiàn)了新的接口,即便底層實(shí)現(xiàn)類并沒有實(shí)現(xiàn)這些接口也無所謂槐雾。
當(dāng)引入接口的方法被調(diào)用時(shí)夭委,代理會(huì)把此調(diào)用委托給實(shí)現(xiàn)了新接口的某個(gè)其他對象。實(shí)際上就是一個(gè)bean的實(shí)現(xiàn)被拆分到了多個(gè)類中募强。
舉個(gè)栗子株灸,為Performance實(shí)現(xiàn)引入Encoreable接口:
public interface Encoreable{
void performEncore();
}
首先我們創(chuàng)建一個(gè)新切面:
@Aspect
public class EncoreableIntroducer{
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
與之前的切面不同,EncoreableIntroducer通過的是@DeclareParants注解將Encoreable接口引入到Performance bean中钻注。和其他切面一樣蚂且,我們需要在Spring中將EncoreableIntroducer聲明為一個(gè)bean。
Spring的自動(dòng)代理機(jī)制會(huì)獲取到此聲明幅恋。注解和自動(dòng)代理提供了一種很便利的方式來創(chuàng)建切面。簡單且涉及極少Spring配置泵肄。但面向注解的切面聲明有一個(gè)明顯劣勢:必須能夠?yàn)橥ㄖ愄砑幼⒔饫弧_@說明必須有源碼淑翼。如果不想將AspectJ放到代碼中,可在Spring XML中配置品追。因?yàn)楣ぷ髦胁挥脁ml玄括,我這里就不說啦。
注入AspectJ切面
如果在執(zhí)行通知時(shí)肉瓦,切面依賴于一個(gè)或多個(gè)類遭京,我們可以在切面內(nèi)部實(shí)例化這些協(xié)作對象。更好的方法是借助Spring的依賴注入把bean裝配進(jìn)AspectJ切面中泞莉。
總結(jié)一下
AOP是OOP的一個(gè)強(qiáng)大補(bǔ)充哪雕。通過AspectJ,我們可以把分散在應(yīng)用各處的行為放入可重用的模塊中鲫趁。通過顯示地聲明在何處如何應(yīng)用該行為斯嚎,可以有效的減少代碼冗余,讓我們的類關(guān)注自身的主要功能挨厚。