Spring提供幾種現(xiàn)成的通知類型并可擴(kuò)展提供任意的通知類型。讓我們看看基本概念和 標(biāo)準(zhǔn)的通知類型。
5.3.2.1. Interception around advice
Spring中最基本的通知類型是interception around advice .
Spring使用方法攔截器的around通知是和AOP聯(lián)盟接口兼容的。實(shí)現(xiàn)around通知的 類需要實(shí)現(xiàn)接口MethodInterceptor:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()方法的MethodInvocation 參數(shù)暴露將被調(diào)用的方法、目標(biāo)連接點(diǎn)、AOP代理和傳遞給被調(diào)用方法的參數(shù)。 invoke()方法應(yīng)該返回調(diào)用的結(jié)果:連接點(diǎn)的返回值。
一個(gè)簡單的MethodInterceptor實(shí)現(xiàn)看起來如下:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
注意MethodInvocation的proceed()方法的調(diào)用。 這個(gè)調(diào)用會(huì)應(yīng)用到目標(biāo)連接點(diǎn)的攔截器鏈中的每一個(gè)攔截器。大部分?jǐn)r截器會(huì)調(diào)用這個(gè)方法,并返回它的返回值。但是, 一個(gè)MethodInterceptor,和任何around通知一樣,可以返回不同的值或者拋出一個(gè)異常,而 不調(diào)用proceed方法。但是,沒有好的原因你要這么做。
Before通知是一種簡單的通知類型。 這個(gè)通知不需要一個(gè)MethodInvocation對(duì)象,因?yàn)樗辉谶M(jìn)入一個(gè)方法 前被調(diào)用。
Before通知的主要優(yōu)點(diǎn)是它不需要調(diào)用proceed() 方法, 因此沒有無意中忘掉繼續(xù)執(zhí)行攔截器鏈的可能性。
MethodBeforeAdvice接口如下所示。 (Spring的API設(shè)計(jì)允許成員變量的before通知,雖然一般的對(duì)象都可以應(yīng)用成員變量攔截,但Spring 有可能永遠(yuǎn)不會(huì)實(shí)現(xiàn)它)。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
注意返回類型是void。 Before通知可以在連接點(diǎn)執(zhí)行之前 插入自定義的行為,但是不能改變返回值。如果一個(gè)before通知拋出一個(gè)異常,這將中斷攔截器 鏈的進(jìn)一步執(zhí)行。這個(gè)異常將沿著攔截器鏈后退著向上傳播。如果這個(gè)異常是unchecked的,或者 出現(xiàn)在被調(diào)用的方法的簽名中,它將會(huì)被直接傳遞給客戶代碼;否則,它將被AOP代理包裝到一個(gè)unchecked 的異常里。
下面是Spring中一個(gè)before通知的例子,這個(gè)例子計(jì)數(shù)所有正常返回的方法:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
如果連接點(diǎn)拋出異常,Throws通知 在連接點(diǎn)返回后被調(diào)用。Spring提供強(qiáng)類型的throws通知。注意這意味著 org.springframework.aop.ThrowsAdvice接口不包含任何方法: 它是一個(gè)標(biāo)記接口,標(biāo)識(shí)給定的對(duì)象實(shí)現(xiàn)了一個(gè)或多個(gè)強(qiáng)類型的throws通知方法。這些方法形式 如下:
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一個(gè)參數(shù)是必需的。 這樣從一個(gè)參數(shù)到四個(gè)參數(shù),依賴于通知是否對(duì)方法和方法 的參數(shù)感興趣。下面是throws通知的例子。
如果拋出RemoteException異常(包括子類), 這個(gè)通知會(huì)被調(diào)用
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
如果拋出ServletException異常, 下面的通知會(huì)被調(diào)用。和上面的通知不一樣,它聲明了四個(gè)參數(shù),所以它可以訪問被調(diào)用的方法,方法的參數(shù) 和目標(biāo)對(duì)象:
public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something will all arguments
}
}
最后一個(gè)例子演示了如何在一個(gè)類中使用兩個(gè)方法來同時(shí)處理 RemoteException和ServletException 異常。任意個(gè)數(shù)的throws方法可以被組合在一個(gè)類中。
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something will all arguments
}
}
5.3.2.4. After Returning通知
Spring中的after returning通知必須實(shí)現(xiàn) org.springframework.aop.AfterReturningAdvice 接口,如下所示:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
After returning通知可以訪問返回值(不能改變)、被調(diào)用的方法、方法的參數(shù)和 目標(biāo)對(duì)象。
下面的after returning通知統(tǒng)計(jì)所有成功的沒有拋出異常的方法調(diào)用:
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
這方法不改變執(zhí)行路徑。如果它拋出一個(gè)異常,這個(gè)異常而不是返回值將被沿著攔截器鏈 向上拋出。
Spring將introduction通知看作一種特殊類型的攔截通知。
Introduction需要實(shí)現(xiàn)IntroductionAdvisor, 和IntroductionInterceptor接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
繼承自AOP聯(lián)盟MethodInterceptor接口的 invoke()方法必須實(shí)現(xiàn)導(dǎo)入:也就是說,如果被調(diào)用的方法是在 導(dǎo)入的接口中,導(dǎo)入攔截器負(fù)責(zé)處理這個(gè)方法調(diào)用,它不能調(diào)用proceed() 方法。
Introduction通知不能被用于任何切入點(diǎn),因?yàn)樗荒茏饔糜陬悓哟紊希皇欠椒ā?你可以只用InterceptionIntroductionAdvisor來實(shí)現(xiàn)導(dǎo)入通知,它有下面的方法:
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {
ClassFilter getClassFilter();
IntroductionInterceptor getIntroductionInterceptor();
Class[] getInterfaces();
}
這里沒有MethodMatcher,因此也沒有和導(dǎo)入通知關(guān)聯(lián)的 切入點(diǎn)。只有類過濾是合乎邏輯的。
getInterfaces()方法返回advisor導(dǎo)入的接口。
讓我們看看一個(gè)來自Spring測(cè)試套件中的簡單例子。我們假設(shè)想要導(dǎo)入下面的接口到一個(gè) 或者多個(gè)對(duì)象中:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
這個(gè)例子演示了一個(gè)mixin。我們想要能夠 將被通知對(duì)象類型轉(zhuǎn)換為Lockable,不管它們的類型,并且調(diào)用lock和unlock方法。如果我們調(diào)用 lock()方法,我們希望所有setter方法拋出LockedException異常。 這樣我們能添加一個(gè)方面使的對(duì)象不可變,而它們不需要知道這一點(diǎn):這是一個(gè)很好的AOP例 子。
首先,我們需要一個(gè)做大量轉(zhuǎn)化的IntroductionInterceptor。 在這里,我們繼承 org.springframework.aop.support.DelegatingIntroductionInterceptor 實(shí)用類。我們可以直接實(shí)現(xiàn)IntroductionInterceptor接口,但是大多數(shù)情況下 DelegatingIntroductionInterceptor是最合適的。
DelegatingIntroductionInterceptor的設(shè)計(jì)是將導(dǎo)入 委托到真正實(shí)現(xiàn)導(dǎo)入接口的接口,隱藏完成這些工作的攔截器。委托可以使用構(gòu)造方法參數(shù) 設(shè)置到任何對(duì)象中;默認(rèn)的委托就是自己(當(dāng)無參數(shù)的構(gòu)造方法被使用時(shí))。這樣在下面的 例子里,委托是DelegatingIntroductionInterceptor的子類 LockMixin。給定一個(gè)委托(默認(rèn)是自身)的 DelegatingIntroductionInterceptor實(shí)例尋找被這個(gè)委托(而不 是IntroductionInterceptor)實(shí)現(xiàn)的所有接口,并支持它們中任何一個(gè)導(dǎo)入。子類如 LockMixin也可能調(diào)用suppressInterflace(Class intf) 方法隱藏不應(yīng)暴露的接口。然而,不管IntroductionInterceptor 準(zhǔn)備支持多少接口,IntroductionAdvisor將控制哪個(gè)接口將被實(shí)際 暴露。一個(gè)導(dǎo)入的接口將隱藏目標(biāo)的同一個(gè)接口的所有實(shí)現(xiàn)。
這樣,LockMixin繼承DelegatingIntroductionInterceptor 并自己實(shí)現(xiàn)Lockable。父類自動(dòng)選擇支持導(dǎo)入的Lockable,所以我們不需要指定它。 用這種方法我們可以導(dǎo)入任意數(shù)量的接口。
注意locked實(shí)例變量的使用。這有效地添加額外的狀態(tài)到目標(biāo) 對(duì)象。
public class LockMixin extends DelegatingIntroductionInterceptor
implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
throw new LockedException();
return super.invoke(invocation);
}
}
通常不要需要改寫invoke()方法:實(shí)現(xiàn) DelegatingIntroductionInterceptor就足夠了,如果是導(dǎo)入的方法, DelegatingIntroductionInterceptor實(shí)現(xiàn)會(huì)調(diào)用委托方法, 否則繼續(xù)沿著連接點(diǎn)處理。在現(xiàn)在的情況下,我們需要添加一個(gè)檢查:在上鎖 狀態(tài)下不能調(diào)用setter方法。
所需的導(dǎo)入advisor是很簡單的。只有保存一個(gè)獨(dú)立的 LockMixin實(shí)例,并指定導(dǎo)入的接口,在這里就是 Lockable。一個(gè)稍微復(fù)雜一點(diǎn)例子可能需要一個(gè)導(dǎo)入攔截器(可以 定義成prototype)的引用:在這種情況下,LockMixin沒有相關(guān)配置,所以我們簡單地 使用new來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
我們可以非常簡單地使用這個(gè)advisor:它不需要任何配置。(但是,有一點(diǎn) 是必要的:就是不可能在沒有IntroductionAdvisor 的情況下使用IntroductionInterceptor。) 和導(dǎo)入一樣,通常 advisor必須是針對(duì)每個(gè)實(shí)例的,并且是有狀態(tài)的。我們會(huì)有不同的的LockMixinAdvisor 每個(gè)被通知對(duì)象,會(huì)有不同的LockMixin。 advisor組成了被通知對(duì)象的狀態(tài)的一部分。
和其他advisor一樣,我們可以使用 Advised.addAdvisor() 方法以編程地方式使用這種advisor,或者在XML中配置(推薦這種方式)。 下面將討論所有代理創(chuàng)建,包括“自動(dòng)代理創(chuàng)建者”,選擇代理創(chuàng)建以正確地處理導(dǎo)入和有狀態(tài)的混入。