1 Spring的通知類型
現(xiàn)在讓我們看看Spring AOP是如何處理通知的。
1.1. 通知的生命周期
Spring的通知可以跨越多個被通知對象共享,或者每個被通知對象有自己的通知。這分別對應(yīng) per-class或per-instance 通知。
Per-class通知使用最為廣泛。它適合于通用的通知,如事務(wù)adisor。它們不依賴被代理 的對象的狀態(tài),也不添加新的狀態(tài)。它們僅僅作用于方法和方法的參數(shù)。
Per-instance通知適合于導(dǎo)入,來支持混入(mixin)。在這種情況下,通知添加狀態(tài)到 被代理的對象。
可以在同一個AOP代理中混合使用共享和per-instance通知。
1.2. Spring中通知類型
Spring提供幾種現(xiàn)成的通知類型并可擴展提供任意的通知類型。讓我們看看基本概念和標(biāo)準(zhǔn)的通知類型。
1.2.1. Interception around advice
Spring中最基本的通知類型是interception around advice .
Spring使用方法攔截器的around通知是和AOP聯(lián)盟接口兼容的。實現(xiàn)around通知的 類需要實現(xiàn)接口MethodInterceptor:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()方法的MethodInvocation 參數(shù)暴露將被調(diào)用的方法、目標(biāo)連接點、AOP代理和傳遞給被調(diào)用方法的參數(shù)。 invoke()方法應(yīng)該返回調(diào)用的結(jié)果:連接點的返回值。
一個簡單的MethodInterceptor實現(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)用。這個調(diào)用會應(yīng)用到目標(biāo)連接點的攔截器鏈中的每一個攔截器。大部分?jǐn)r截器會調(diào)用這個方法,并返回它的返回值。但是, 一個MethodInterceptor,和任何around通知一樣,可以返回不同的值或者拋出一個異常,而 不調(diào)用proceed方法。但是,沒有好的原因你要這么做。
MethodInterceptor提供了和其他AOP聯(lián)盟的兼容實現(xiàn)的交互能力。這一節(jié)下面 要討論的其他的通知類型實現(xiàn)了AOP公共的概念,但是以Spring特定的方式。雖然使用特定 通知類型有很多優(yōu)點,但如果你可能需要在其他的AOP框架中使用,請堅持使用MethodInterceptor around通知類型。注意目前切入點不能和其它框架交互操作,并且AOP聯(lián)盟目前也沒有定義切入 點接口。
1.2.2. Before通知
Before通知是一種簡單的通知類型。 這個通知不需要一個MethodInvocation對象,因為它只在進入一個方法前被調(diào)用。
Before通知的主要優(yōu)點是它不需要調(diào)用proceed() 方法, 因此沒有無意中忘掉繼續(xù)執(zhí)行攔截器鏈的可能性。
MethodBeforeAdvice接口如下所示。 (Spring的API設(shè)計允許成員變量的before通知,雖然一般的對象都可以應(yīng)用成員變量攔截,但Spring 有可能永遠不會實現(xiàn)它)。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
注意返回類型是void。 Before通知可以在連接點執(zhí)行之前 插入自定義的行為,但是不能改變返回值。如果一個before通知拋出一個異常,這將中斷攔截器 鏈的進一步執(zhí)行。這個異常將沿著攔截器鏈后退著向上傳播。如果這個異常是unchecked的,或者 出現(xiàn)在被調(diào)用的方法的簽名中,它將會被直接傳遞給客戶代碼;否則,它將被AOP代理包裝到一個unchecked 的異常里。
下面是Spring中一個before通知的例子,這個例子計數(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;
}
}
Before通知可以被用于任何類型的切入點。
1.2.3. Throws通知
如果連接點拋出異常,Throws通知 在連接點返回后被調(diào)用。Spring提供強類型的throws通知。注意這意味著 org.springframework.aop.ThrowsAdvice接口不包含任何方法: 它是一個標(biāo)記接口,標(biāo)識給定的對象實現(xiàn)了一個或多個強類型的throws通知方法。這些方法形式 如下:
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一個參數(shù)是必需的。這樣從一個參數(shù)到四個參數(shù),依賴于通知是否對方法和方法 的參數(shù)感興趣。下面是throws通知的例子。
如果拋出RemoteException異常(包括子類), 這個通知會被調(diào)用
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
如果拋出ServletException異常, 下面的通知會被調(diào)用。和上面的通知不一樣,它聲明了四個參數(shù),所以它可以訪問被調(diào)用的方法,方法的參數(shù)和目標(biāo)對象:
public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something will all arguments
}
}
最后一個例子演示了如何在一個類中使用兩個方法來同時處理 RemoteException和ServletException 異常。任意個數(shù)的throws方法可以被組合在一個類中。
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
}
}
Throws通知可被用于任何類型的切入點。
1.2.4. After Returning通知
Spring中的after returning通知必須實現(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)對象。
下面的after returning通知統(tǒng)計所有成功的沒有拋出異常的方法調(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í)行路徑。如果它拋出一個異常,這個異常而不是返回值將被沿著攔截器鏈向上拋出。
After returning通知可被用于任何類型的切入點。
1.2.5. Introduction通知
Spring將introduction通知看作一種特殊類型的攔截通知。
Introduction需要實現(xiàn)IntroductionAdvisor, 和IntroductionInterceptor接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
繼承自AOP聯(lián)盟MethodInterceptor接口的 invoke()方法必須實現(xiàn)導(dǎo)入:也就是說,如果被調(diào)用的方法是在 導(dǎo)入的接口中,導(dǎo)入攔截器負(fù)責(zé)處理這個方法調(diào)用,它不能調(diào)用proceed() 方法。
Introduction通知不能被用于任何切入點,因為它只能作用于類層次上,而不是方法。你可以只用InterceptionIntroductionAdvisor來實現(xiàn)導(dǎo)入通知,它有下面的方法:
public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {
ClassFilter getClassFilter();
IntroductionInterceptor getIntroductionInterceptor();
Class[] getInterfaces();
}
這里沒有MethodMatcher,因此也沒有和導(dǎo)入通知關(guān)聯(lián)的 切入點。只有類過濾是合乎邏輯的。
getInterfaces()方法返回advisor導(dǎo)入的接口。
讓我們看看一個來自Spring測試套件中的簡單例子。我們假設(shè)想要導(dǎo)入下面的接口到一個 或者多個對象中:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
這個例子演示了一個mixin。我們想要能夠 將被通知對象類型轉(zhuǎn)換為Lockable,不管它們的類型,并且調(diào)用lock和unlock方法。如果我們調(diào)用 lock()方法,我們希望所有setter方法拋出LockedException異常。這樣我們能添加一個方面使的對象不可變,而它們不需要知道這一點:這是一個很好的AOP例 子。
首先,我們需要一個做大量轉(zhuǎn)化的IntroductionInterceptor。 在這里,我們繼承 org.springframework.aop.support.DelegatingIntroductionInterceptor 實用類。我們可以直接實現(xiàn)IntroductionInterceptor接口,但是大多數(shù)情況下 DelegatingIntroductionInterceptor是最合適的。
DelegatingIntroductionInterceptor的設(shè)計是將導(dǎo)入 委托到真正實現(xiàn)導(dǎo)入接口的接口,隱藏完成這些工作的攔截器。委托可以使用構(gòu)造方法參數(shù) 設(shè)置到任何對象中;默認(rèn)的委托就是自己(當(dāng)無參數(shù)的構(gòu)造方法被使用時)。這樣在下面的例子里,委托是DelegatingIntroductionInterceptor的子類 LockMixin。給定一個委托(默認(rèn)是自身)的 DelegatingIntroductionInterceptor實例尋找被這個委托(而不 是IntroductionInterceptor)實現(xiàn)的所有接口,并支持它們中任何一個導(dǎo)入。子類如 LockMixin也可能調(diào)用suppressInterflace(Class intf) 方法隱藏不應(yīng)暴露的接口。然而,不管IntroductionInterceptor 準(zhǔn)備支持多少接口,IntroductionAdvisor將控制哪個接口將被實際 暴露。一個導(dǎo)入的接口將隱藏目標(biāo)的同一個接口的所有實現(xiàn)。
這樣,LockMixin繼承DelegatingIntroductionInterceptor 并自己實現(xiàn)Lockable。父類自動選擇支持導(dǎo)入的Lockable,所以我們不需要指定它。用這種方法我們可以導(dǎo)入任意數(shù)量的接口。
注意locked實例變量的使用。這有效地添加額外的狀態(tài)到目標(biāo) 對象。
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()方法:實現(xiàn) DelegatingIntroductionInterceptor就足夠了,如果是導(dǎo)入的方法, DelegatingIntroductionInterceptor實現(xiàn)會調(diào)用委托方法, 否則繼續(xù)沿著連接點處理。在現(xiàn)在的情況下,我們需要添加一個檢查:在上鎖狀態(tài)下不能調(diào)用setter方法。
所需的導(dǎo)入advisor是很簡單的。只有保存一個獨立的 LockMixin實例,并指定導(dǎo)入的接口,在這里就是 Lockable。一個稍微復(fù)雜一點例子可能需要一個導(dǎo)入攔截器(可以 定義成prototype)的引用:在這種情況下,LockMixin沒有相關(guān)配置,所以我們簡單地 使用new來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
我們可以非常簡單地使用這個advisor:它不需要任何配置。(但是,有一點 是必要的:就是不可能在沒有IntroductionAdvisor 的情況下使用IntroductionInterceptor。) 和導(dǎo)入一樣,通常 advisor必須是針對每個實例的,并且是有狀態(tài)的。我們會有不同的的LockMixinAdvisor 每個被通知對象,會有不同的LockMixin。 advisor組成了被通知對象的狀態(tài)的一部分。
和其他advisor一樣,我們可以使用 Advised.addAdvisor() 方法以編程地方式使用這種advisor,或者在XML中配置(推薦這種方式)。 下面將討論所有代理創(chuàng)建,包括“自動代理創(chuàng)建者”,選擇代理創(chuàng)建以正確地處理導(dǎo)入和有狀態(tài)的混入。
參考資料:
1. http://www.javaresearch.org/article/showarticle.jsp?column=23&thread=41315
2. http://tech.ccidnet.com/art/1112/20051114/371959_5.html
3. http://www.7dspace.com/doc/21/0603/20063305365394884.htm
4. http://barton131420.cnblogs.com/articles/280664.html
5. http://www.opentown.info/bbs/viewtopic.php?t=7