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