?
本文檔使用 PHP中文網手冊 發(fā)布
現在讓我們看一下SPring AOP是怎樣處理通知的。
每個通知都是一個Spring bean。一個通知實例既可以被所有被通知的對象共享,也可以被每個被通知對象獨占。 這根據設置類共享(per-class)或基于實例(per-instance)的參數來決定。
類共享通知經常會被用到。它很適合用作通用的通知例如事務通知器(advisor)。這些通知器不依賴于代理對象的狀態(tài)也不會向代理對象添加新的狀態(tài); 它們僅僅在方法和參數上起作用。
基于實例的通知很適合用作導入器來支持混合類型(mixin)。在這種情況下,通知向代理對象添加狀態(tài)。
在同一個AOP代理里混合使用類共享和基于實例的通知是可能的。
Spring自帶了多種通知類型,而且它們也可以被擴展來支持各種通知類型。讓我們先看看基本概念和標準的通知類型。
在Spring中最基礎的通知類型是攔截環(huán)繞通知(interception around advice)。
Spring里使用方法攔截的環(huán)繞通知兼容AOP聯(lián)盟接口。實現環(huán)繞通知的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()
方法的調用。
這個方法繼續(xù)運行指向連接點的攔截器鏈并返回proceed()的結果。大多數攔截器會調用這個方法,返回一個值。
然而,一個類似任意環(huán)繞通知的MethodInterceptor,可以返回一個不同的值或者拋出一個異常而不是調用proceed方法。
但除非你有很好的理由,否則不要考慮這樣做!
MethodInterceptor提供了與其它AOP聯(lián)盟兼容實現的互操作性。本節(jié)的剩下部分將討論其它的通知類型, 它們實現了通用的AOP概念,但是以一種Spring風格的方式來實現的。使用最通用的通知類型還有一個好處, 固定使用MethodInterceptor環(huán)繞通知可以讓你在其它的AOP框架里運行你所定制的切面。 要注意現在切入點還不能和其它框架進行互操作,AOP聯(lián)盟目前還沒有定義切入點接口。
一個更簡單的通知類型是前置通知(before advice)。
它不需要MethodInvocation
對象,因為它只是在進入方法之前被調用。
前置通知的一個主要優(yōu)點是它不需要調用proceed()
方法,
因此就不會發(fā)生無意間運行攔截器鏈失敗的情況。
下面是MethodBeforeAdvice
接口。
(Spring的API設計能夠為類中的成員變量提供前置通知,雖然通常對象會被應用于成員變量攔截之上,但看起來Spring并不打算實現這個功能。)
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; }
注意返回值的類型是void
。前置通知可以在連接點執(zhí)行之前插入自定義行為,但是不能修改連接點的返回值。
如果一個前置通知拋出異常,這將中止攔截器鏈的進一步執(zhí)行。異常將沿著攔截器鏈向回傳播。
如果異常是非強制檢查的(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; } }
前置通知可以和任何切入點一起使用。
如果連接點拋出異常,異常通知(throws advice)將在連接點返回后被調用。
Spring提供類型檢查的異常通知(typed throws advice),這意味著org.springframework.aop.ThrowsAdvice
接口不包含任何方法:
它只是一個標記接口用來標識所給對象實現了一個或者多個針對特定類型的異常通知方法。這些方法應當滿足下面的格式:
afterThrowing([Method, args, target], subclassOfThrowable)
只有最后一個參數是必須的。根據異常通知方法對方法及參數的需求,方法的簽名可以有一個或者四個參數。 下面是一些異常通知的例子。
當一個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 with 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 with all arguments } }
異常通知可以和任何切入點一起使用。
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; } }
這個通知不改變執(zhí)行路徑。如果拋出異常,它將沿著攔截器鏈被拋出而不是返回被調用方法的執(zhí)行結果。
后置通知可以和任何切入點一起使用。
Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進行處理。
引入通知需要一個IntroductionAdvisor
,
和一個IntroductionInterceptor
, 后者實現下面的接口:
public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); }
繼承自AOP聯(lián)盟MethodInterceptor
接口的invoke()
方法,必須確保實現引入:
也就是說,如果被調用的方法位于一個已經被引入接口里,這個引入攔截器將負責完成對這個方法的調用--因為它不能調用proceed()
方法。
引入通知不能和任何切入點一起使用,因為它是應用在類級別而不是方法級別。
你只能通過IntroductionAdvisor
來使用引入通知,這個接口包括下面的方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo { ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo { Class[] getInterfaces(); }
這里沒有MethodMatcher
接口,因此也就沒有
Pointcut
與引入通知相關聯(lián)。這里只進行類過濾。
getInterfaces()
方法返回這個通知器所引入的接口。
validateInterfaces()
方法將被內部使用來查看被引入的接口是否能夠由配置的IntroductionInterceptor
來實現。
讓我們看看從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
可以調用suppressInterface(Class intf)
方法來禁止那些不應該被暴露的接口。
然而,不論IntroductionInterceptor
支持多少接口,
IntroductionAdvisor
的使用將控制哪些接口真正被暴露。
一個被引入的接口將覆蓋目標對象實現的相同接口。
這樣LockMixin就繼承了DelegatingIntroductionInterceptor
并實現了Lockable接口。
這里父類會自動選擇Lockable接口并提供引入支持,因此我們不需要配置它。用這種方法我們能夠引入任意數量的接口。
注意locked
實例變量的用法。這有效地向目標對象增加了額外狀態(tài)。
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)狀態(tài),沒有setter方法可以被調用。
引入通知器的要求是很簡單的。它的全部要求只是保持一個特定的LockMixin
實例,
并說明被通知的接口 - 在這個例子里,只有一個Lockable
接口。
更復雜的例子中也許會引用一個引入攔截器的(可以被定義為一個原型):
在這種情況下,不需要對LockMixin
進行相關配置,
因此我們可以簡單的用new
關鍵字來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } }
我們可以很容易應用這個通知器:它不需要配置。
(然而,下面是必須記住的:不可以在沒有IntroductionAdvisor的情況下使用IntroductionInterceptor
。)
通常的引入通知器必須是基于實例的,因為它是有狀態(tài)的。因此,對于每個被通知對象我們需要一個不同
實例的LockMixinAdvisor
和LockMixin
。
這種情況下通知器保存了被通知對象的部分狀態(tài)。
我們能夠通過使用Advised.addAdvisor()
的編程方式來應用通知器,或者像其它通知器那樣(也是推薦的方式)在XML里進行配置。全部的代理創(chuàng)建選擇(包括“自動代理創(chuàng)建器”)將在下面進行討論,
看看如何正確地處理引入和有狀態(tài)混合類型。