?
Dokumen ini menggunakan Manual laman web PHP Cina Lepaskan
現(xiàn)在讓我們看一下SPring AOP是怎樣處理通知的。
每個通知都是一個Spring bean。一個通知實(shí)例既可以被所有被通知的對象共享,也可以被每個被通知對象獨(dú)占。 這根據(jù)設(shè)置類共享(per-class)或基于實(shí)例(per-instance)的參數(shù)來決定。
類共享通知經(jīng)常會被用到。它很適合用作通用的通知例如事務(wù)通知器(advisor)。這些通知器不依賴于代理對象的狀態(tài)也不會向代理對象添加新的狀態(tài); 它們僅僅在方法和參數(shù)上起作用。
基于實(shí)例的通知很適合用作導(dǎo)入器來支持混合類型(mixin)。在這種情況下,通知向代理對象添加狀態(tài)。
在同一個AOP代理里混合使用類共享和基于實(shí)例的通知是可能的。
Spring自帶了多種通知類型,而且它們也可以被擴(kuò)展來支持各種通知類型。讓我們先看看基本概念和標(biāo)準(zhǔn)的通知類型。
在Spring中最基礎(chǔ)的通知類型是攔截環(huán)繞通知(interception around advice)。
Spring里使用方法攔截的環(huán)繞通知兼容AOP聯(lián)盟接口。實(shí)現(xiàn)環(huán)繞通知的MethodInterceptor應(yīng)當(dāng)實(shí)現(xiàn)下面的接口:
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
invoke()
方法的MethodInvocation
參數(shù)暴露了被調(diào)用的方法;
目標(biāo)連接點(diǎn);AOP代理以及傳遞給方法的參數(shù)。invoke()
方法應(yīng)該返回調(diào)用的結(jié)果:即連接點(diǎn)的返回值。
一個簡單的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)用。
這個方法繼續(xù)運(yùn)行指向連接點(diǎn)的攔截器鏈并返回proceed()的結(jié)果。大多數(shù)攔截器會調(diào)用這個方法,返回一個值。
然而,一個類似任意環(huán)繞通知的MethodInterceptor,可以返回一個不同的值或者拋出一個異常而不是調(diào)用proceed方法。
但除非你有很好的理由,否則不要考慮這樣做!
MethodInterceptor提供了與其它AOP聯(lián)盟兼容實(shí)現(xiàn)的互操作性。本節(jié)的剩下部分將討論其它的通知類型, 它們實(shí)現(xiàn)了通用的AOP概念,但是以一種Spring風(fēng)格的方式來實(shí)現(xiàn)的。使用最通用的通知類型還有一個好處, 固定使用MethodInterceptor環(huán)繞通知可以讓你在其它的AOP框架里運(yùn)行你所定制的切面。 要注意現(xiàn)在切入點(diǎn)還不能和其它框架進(jìn)行互操作,AOP聯(lián)盟目前還沒有定義切入點(diǎn)接口。
一個更簡單的通知類型是前置通知(before advice)。
它不需要MethodInvocation
對象,因?yàn)樗皇窃谶M(jìn)入方法之前被調(diào)用。
前置通知的一個主要優(yōu)點(diǎn)是它不需要調(diào)用proceed()
方法,
因此就不會發(fā)生無意間運(yùn)行攔截器鏈?zhǔn)〉那闆r。
下面是MethodBeforeAdvice
接口。
(Spring的API設(shè)計(jì)能夠?yàn)轭愔械某蓡T變量提供前置通知,雖然通常對象會被應(yīng)用于成員變量攔截之上,但看起來Spring并不打算實(shí)現(xiàn)這個功能。)
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; }
注意返回值的類型是void
。前置通知可以在連接點(diǎn)執(zhí)行之前插入自定義行為,但是不能修改連接點(diǎn)的返回值。
如果一個前置通知拋出異常,這將中止攔截器鏈的進(jìn)一步執(zhí)行。異常將沿著攔截器鏈向回傳播。
如果異常是非強(qiáng)制檢查的(unchecked)或者已經(jīng)被包含在被調(diào)用方法的throws聲明中,它將被直接返回給客戶端;
否則它將由AOP代理包裝在一個非強(qiáng)制檢查異常中返回。
這是一個Spring前置通知的例子,它計(jì)算所有方法被調(diào)用的次數(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)一起使用。
如果連接點(diǎn)拋出異常,異常通知(throws advice)將在連接點(diǎn)返回后被調(diào)用。
Spring提供類型檢查的異常通知(typed throws advice),這意味著org.springframework.aop.ThrowsAdvice
接口不包含任何方法:
它只是一個標(biāo)記接口用來標(biāo)識所給對象實(shí)現(xiàn)了一個或者多個針對特定類型的異常通知方法。這些方法應(yīng)當(dāng)滿足下面的格式:
afterThrowing([Method, args, target], subclassOfThrowable)
只有最后一個參數(shù)是必須的。根據(jù)異常通知方法對方法及參數(shù)的需求,方法的簽名可以有一個或者四個參數(shù)。 下面是一些異常通知的例子。
當(dāng)一個RemoteException
(包括它的子類)被拋出時,下面的通知會被調(diào)用:
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
當(dāng)一個ServletException被拋出,下面的通知將被調(diào)用。 和上面的通知不同,它聲明了4個參數(shù),因此它可以訪問被調(diào)用的方法,方法的參數(shù)以及目標(biāo)對象:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
最后一個例子說明怎樣在同一個類里使用兩個方法來處理
RemoteException
和ServletException
??梢栽谝粋€類里組合任意數(shù)量的異常通知方法。
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 } }
異常通知可以和任何切入點(diǎn)一起使用。
Spring中的后置通知(After Returning advice)必須實(shí)現(xiàn) org.springframework.aop.AfterReturningAdvice 接口,像下面顯示的那樣:
public interface AfterReturningAdvice extends Advice { void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; }
后置通知可以訪問返回值(但不能進(jìn)行修改),被調(diào)用方法,方法參數(shù)以及目標(biāo)對象。
下面的后置通知計(jì)算所有運(yùn)行成功(沒有拋出異常)的方法調(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í)行路徑。如果拋出異常,它將沿著攔截器鏈被拋出而不是返回被調(diào)用方法的執(zhí)行結(jié)果。
后置通知可以和任何切入點(diǎn)一起使用。
Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進(jìn)行處理。
引入通知需要一個IntroductionAdvisor
,
和一個IntroductionInterceptor
, 后者實(shí)現(xiàn)下面的接口:
public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); }
繼承自AOP聯(lián)盟MethodInterceptor
接口的invoke()
方法,必須確保實(shí)現(xiàn)引入:
也就是說,如果被調(diào)用的方法位于一個已經(jīng)被引入接口里,這個引入攔截器將負(fù)責(zé)完成對這個方法的調(diào)用--因?yàn)樗荒苷{(diào)用proceed()
方法。
引入通知不能和任何切入點(diǎn)一起使用,因?yàn)樗菓?yīng)用在類級別而不是方法級別。
你只能通過IntroductionAdvisor
來使用引入通知,這個接口包括下面的方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo { ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo { Class[] getInterfaces(); }
這里沒有MethodMatcher
接口,因此也就沒有
Pointcut
與引入通知相關(guān)聯(lián)。這里只進(jìn)行類過濾。
getInterfaces()
方法返回這個通知器所引入的接口。
validateInterfaces()
方法將被內(nèi)部使用來查看被引入的接口是否能夠由配置的IntroductionInterceptor
來實(shí)現(xiàn)。
讓我們看看從Spring測試集里拿來的一個簡單例子。讓我們假設(shè)我們希望把下面的接口引入給一個或者多個對象:
public interface Lockable { void lock(); void unlock(); boolean locked(); }
這里描述了一個混合類型(mixin)。我們希望不論原本對象是什么類型,都把這個被通知對象轉(zhuǎn)換為Lockable接口并可以調(diào)用lock和unlock 方法。
如果我們調(diào)用lock() 方法,我們希望所有的setter 方法拋出一個LockedException
異常。
這樣我們就可以加入一個方面來確保對象在得到通知之前是不可修改的:一個關(guān)于AOP的好例子。
首先,我們需要一個IntroductionInterceptor
來做些粗活。這里,我們擴(kuò)展了
org.springframework.aop.support.DelegatingIntroductionInterceptor
這個方便的類。
我們能夠直接實(shí)現(xiàn)IntroductionInterceptor接口,
但在這個例子里使用DelegatingIntroductionInterceptor
是最好的選擇。
DelegatingIntroductionInterceptor
設(shè)計(jì)為把一個引入托管給一個實(shí)現(xiàn)這個接口的類,
這通過隱藏攔截的使用來實(shí)現(xiàn)。托管可以被設(shè)置到任何具有構(gòu)造器方法的類;這里使用缺省托管(即使用無參構(gòu)造器)。
因此在下面這個例子里,托管者將是DelegatingIntroductionInterceptor
的子類
LockMixin
。
當(dāng)一個托管實(shí)現(xiàn)被提供,DelegatingIntroductionInterceptor
實(shí)例將查找托管所實(shí)現(xiàn)的所有接口
(除了IntroductionInterceptor之外),并為這些接口的介紹提供支持。子類例如LockMixin
可以調(diào)用suppressInterface(Class intf)
方法來禁止那些不應(yīng)該被暴露的接口。
然而,不論IntroductionInterceptor
支持多少接口,
IntroductionAdvisor
的使用將控制哪些接口真正被暴露。
一個被引入的接口將覆蓋目標(biāo)對象實(shí)現(xiàn)的相同接口。
這樣LockMixin就繼承了DelegatingIntroductionInterceptor
并實(shí)現(xiàn)了Lockable接口。
這里父類會自動選擇Lockable接口并提供引入支持,因此我們不需要配置它。用這種方法我們能夠引入任意數(shù)量的接口。
注意locked
實(shí)例變量的用法。這有效地向目標(biāo)對象增加了額外狀態(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
就已經(jīng)夠用了(如果一個方法被引入,這個實(shí)現(xiàn)將調(diào)用實(shí)際的托管方法,否則它將直接處理連接點(diǎn))。在當(dāng)前這個例子里,我們需要增加一個檢查:如果處于加鎖(locked)狀態(tài),沒有setter方法可以被調(diào)用。
引入通知器的要求是很簡單的。它的全部要求只是保持一個特定的LockMixin
實(shí)例,
并說明被通知的接口 - 在這個例子里,只有一個Lockable
接口。
更復(fù)雜的例子中也許會引用一個引入攔截器的(可以被定義為一個原型):
在這種情況下,不需要對LockMixin
進(jìn)行相關(guān)配置,
因此我們可以簡單的用new
關(guān)鍵字來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } }
我們可以很容易應(yīng)用這個通知器:它不需要配置。
(然而,下面是必須記住的:不可以在沒有IntroductionAdvisor的情況下使用IntroductionInterceptor
。)
通常的引入通知器必須是基于實(shí)例的,因?yàn)樗怯袪顟B(tài)的。因此,對于每個被通知對象我們需要一個不同
實(shí)例的LockMixinAdvisor
和LockMixin
。
這種情況下通知器保存了被通知對象的部分狀態(tài)。
我們能夠通過使用Advised.addAdvisor()
的編程方式來應(yīng)用通知器,或者像其它通知器那樣(也是推薦的方式)在XML里進(jìn)行配置。全部的代理創(chuàng)建選擇(包括“自動代理創(chuàng)建器”)將在下面進(jìn)行討論,
看看如何正確地處理引入和有狀態(tài)混合類型。