Spring提供了多種開箱即用的通知類型,而且它們也可以被擴展來支持任何通知類型。讓我們先看看基本概念和標準的通知類型。
在Spring中最基礎的通知類型是攔截around通知。
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()方法的調用。這個方法 繼續運行指向連接點的攔截器鏈并返回proceed()的結果。然而,一個類似任何環繞通知的MethodInterceptor, 可以返回一個不同的值或者拋出一個異常而不是調用proceed方法。但除非你有很好的理由,否則不要考慮這樣做!
![[Note]](http://www.redsaga.com/spring_ref/2.0/images/admons/note.png) |
Note |
MethodInterceptor提供了與其它AOP聯盟兼容實現的互操作性。本節的剩下部分將討論其它的通知類型,它們實現了通用的AOP概念, 但是以一種Spring風格的方式來實現的。使用最通用的通知類型還有一個好處,固定使用MethodInterceptor 環繞通知可以讓你在其它的AOP框架里 運行你所定制的方面。要注意現在切入點還不能和其它框架進行互操作,AOP聯盟目前還沒有定義切入點接口。
|
一個更簡單的通知類型是before 通知。它不需要 MethodInvocation對象,因為它只是在進入方法之前被調用。
前置通知(before advice)的一個主要優點是它不需要調用proceed() 方法,因此就不會發生 無意間運行攔截器鏈失敗的情況。
MethodBeforeAdvice 接口被顯示在下面。(Spring的API設計能夠為類中的成員變量提供前置通知,雖然這可以把通用對象應用到成員變量攔截上,但看起來Spring并不打算實現這個功能。)
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
注意返回值的類型是void。前置通知可以在連接點執行之前插入自定義行為,但是不能修改連接點的返回值。如果一個前置通知拋出異常,這將中止攔截器鏈的進一步執行。 異常將沿著攔截器鏈向回傳播。如果異常是非強制檢查的(unchecked)或者已經被包含在被調用方法的簽名中(譯者:即出現在方法聲明的throws子句中),它將被直接返回給客戶端; 否則它將由AOP代理包裝在一個非強制檢查異常中返回。
這里是Spring里一個前置通知的例子,它計算所有方法被調用的次數:
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;
}
}
![[Tip]](http://www.redsaga.com/spring_ref/2.0/images/admons/tip.png) |
Tip |
前置通知可以和任何切入點一起使用。
|
如果連接點拋出異常,異常通知(throws advice)將在連接點返回后被調用。 Spring提供類型檢查的異常通知,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法:它只是一個標記接口用來標識 所給對象實現了一個或者多個針對特定類型的異常通知方法。這些方法應當滿足下面的格式
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被拋出,下面的通知將被調用。 和上面的通知不同,它聲明了4個參數,因此它可以訪問被調用的方法,方法的參數以及目標對象:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something will all arguments
}
}
最后一個例子說明怎樣在同一個類里使用兩個方法來處理 RemoteException和ServletException。可以在一個類里組合任意數量的異常通知方法。
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
}
}
![[Tip]](http://www.redsaga.com/spring_ref/2.0/images/admons/tip.png) |
Tip |
異常通知可以和任何切入點一起使用。
|
Spring中的一個后置通知(After Returning advice)必須實現 org.springframework.aop.AfterReturningAdvice 接口,像下面顯示的那樣:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
一個后置通知可以訪問返回值(但不能進行修改),被調用方法,方法參數以及目標對象。
下面的后置通知計算所有運行成功(沒有拋出異常)的方法調用:
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;
}
}
這個通知不改變執行路線。如果通知拋出異常,異常將沿著攔截器鏈返回(拋出)而不是返回被調用方法的執行結果。
![[Tip]](http://www.redsaga.com/spring_ref/2.0/images/admons/tip.png) |
Tip |
后置通知可以和任何切入點一起使用。
|
Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進行處理。
引入通知需要一個IntroductionAdvisor, 和一個IntroductionInterceptor, 后者實現下面的接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
invoke() 方法,繼承了AOP聯盟MethodInterceptor 接口,必須確保實現引入: 這里的意思是說,如果被調用的方法位于一個已經被引入接口里,這個引入攔截器將負責完成對這個方法的調用--因為后者不能調用proceed()方法。
引入通知不能和任何切入點一起使用,因為它是應用在類級別而不是方法級別。 你可以通過IntroductionAdvisor來使用引入通知,這個接口包括下面的方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class[] getInterfaces();
}
這里沒有MethodMatcher接口,因此也就沒有 Pointcut接口與引入通知相關聯。這里只進行類過濾。
getInterfaces()方法返回這個advisor所引入的接口。
validateInterfaces()方法將被內部使用來查看被引入的接口是否能夠由配置的IntroductionInterceptor來實現。
讓我們看看從Spring測試集里拿來的一個簡單例子。讓我們假設我們希望把下面的接口引入給一個或者多個對象:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
這里描述了一個混合類型。我們希望不論原本對象是什么類型,都把這個被通知對象轉換為Lockable接口并可以調用lock 和unlock 方法。 如果我們調用lock() 方法,我們希望所有的setter 方法拋出一個LockedException異常。這樣我們就可以加入一個方面來確保對象在得到通知之前是不可修改的:一個關于AOP的好例子。
首先,我們需要一個IntroductionInterceptor來做粗活。這里,我們擴展了 org.springframework.aop.support.DelegatingIntroductionInterceptor這個方便的類。我們能夠直接實現 IntroductionInterceptor接口,但在這個例子里使用DelegatingIntroductionInterceptor是最好的選擇。
DelegatingIntroductionInterceptor設計為把一個引入托管給一個實現這個接口的類, 這通過隱藏攔截的使用來實現。托管可以被設置到任何具有構造器方法的類;這里使用缺省托管(即使用無參構造器)。 因此在下面這個例子里,托管者將是DelegatingIntroductionInterceptor的子類 LockMixin。 當一個托管實現被提供,DelegatingIntroductionInterceptor實例將查找托管所實現的所有接口 (除了IntroductionInterceptor之外),并為這些接口的介紹提供支持。子類例如LockMixin 可以調用suppressInterface(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里面已經包含了一個實現--如果一個方法被引入,這個實現將調用實際的托管方法,否則它將直接處理連接點--通常這已經足夠了。在當前這個例子里,我們需要增加一個檢查:如果處于加鎖(locked)狀態,沒有setter方法可以被調用。
引入處理器的要求是很簡單的。它的全部要求只是保持一個特定的LockMixin實例, 并說明被通知的接口--在這個例子里,只有一個Lockable接口。 一個更復雜的例子也許會獲取一個介紹攔截器的引用(后者可以被定義為一個prototype): 在這種情況下,不需要對LockMixin進行相關配置,因此我們可以簡單的用new關鍵字來創建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
我們可以很容易應用這個advisor:它不需要配置。(然而,下面是必須記住的:不可以在沒有IntroductionAdvisor的情況下使用IntroductionInterceptor。) 對于通常的引入advisor必須是基于實例的,因為它是有狀態的。因此,對于每個被通知對象我們需要一個不同 實例的LockMixinAdvisor和LockMixin。這種情況下advisor保存了被通知對象的部分狀態。
我們能夠通過使用Advised.addAdvisor() 的編程方式來應用advisor,或者像其它advisor那樣(也是推薦的方式)在XML里進行配置。全部的代理創建選擇(包括“自動代理創建器”)將在下面進行討論, 看看如何正確地處理introduction和有狀態混合類型。