Spring AOP APIs
前一章介紹了Spring 2.0中提供的由@AspectJ和基于Schema的兩種切面定義的AOP。在這個章節(jié)里,我們將 討論更底層的Spring AOP API,以及如何在Spring 1.2應(yīng)用中使用這些API。對于新的應(yīng)用程序,我們推薦 使用前一章介紹的Spring 2.0 AOP支持,但是當(dāng)你使用已有系統(tǒng)時,或是閱讀書籍和文章時,很有可能會遇到 Spring 1.2風(fēng)格的例子。Spring 2.0是完全向前兼容Spring 1.2的,這一章中涉及的所有內(nèi)容在Spring 2.0里面得到了完全的支持。
讓我們看看Spring是如何處理切入點這個重要概念的。
Spring的切入點模型使得切入點可以獨立于通知類型進(jìn)行重用,這就使得針對不同 advice使用相同的pointcut成為可能。
org.springframework.aop.Pointcut 是最核心的接口,用來將 通知應(yīng)用于特定的類和方法,完整的接口定義如下:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
將Pointcut接口分割成有利于重用類和方法匹配的兩部分,以及進(jìn)行更細(xì)粒度的操作組合(例如與另一個方法匹配實現(xiàn)進(jìn)行“或操作”)。
ClassFilter接口用來將切入點限定在一個給定的類集合中。如果matches()方法總是返回true,所有目標(biāo)類都將被匹配:
public interface ClassFilter { boolean matches(Class clazz); }
MethodMatcher接口通常更重要,完整的接口定義如下:
public interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); }
matches(Method, Class)方法用來測試這個切入點是否匹配目標(biāo)類的指定方法。這將在AOP代理被創(chuàng)建的時候執(zhí)行,這樣可以避免在每次方法調(diào)用的時候都執(zhí)行。如果matches(Method, Class )對于一個給定的方法返回true,并且isRuntime() 也返回true,那么matches(Method, Class , Object[])將在每個方法調(diào)用的時候被調(diào)用。這使得切入點在通知將被執(zhí)行前可以查看傳入到方法的參數(shù)。
大多數(shù)MethodMatcher是靜態(tài)的,這意味著isRuntime()方法返回 false。在這種情況下,matches(Method, Class , Object[])永遠(yuǎn)不會被調(diào)用。
![]() |
Tip |
---|---|
應(yīng)盡可能地使切入點是靜態(tài)的,這就允許AOP框架在AOP代理被創(chuàng)建時緩存對切入點的計算結(jié)果。 |
Spring在切入點上支持以下運算: 或和與。
-
或運算表示只需有一個切入點被匹配就執(zhí)行方法。
-
與運算表示所有的切入點都匹配的情況下才執(zhí)行。
-
通常或運算要更有用一些。
-
切入點可以使用org.springframework.aop.support.Pointcuts類中的靜態(tài)方法來編寫,或者使用同一個包內(nèi)的ComposablePointcut類。不過使用AspectJ切入點表達(dá)式通常會更簡單一些。
從2.0開始,Spring中使用的最重要的切入點類型是org.springframework.aop.aspectj.AspectJExpressionPointcut。 這個切入點使用AspectJ提供的庫來解析滿足AspectJ語法切入點表達(dá)式字符串。
可以查看前一章關(guān)于所支持的AspectJ切入點原語的討論。
Spring提供了一些很方便的切入點實現(xiàn)。一些是開箱即用的,其它的是切入點應(yīng)用規(guī)范的子集。
靜態(tài)切入點基于方法和目標(biāo)類進(jìn)行切入點判斷而不考慮方法的參數(shù)。在多數(shù)情況下,靜態(tài)切入點是高效的、最好的選擇。 Spring只在第一次調(diào)用方法時執(zhí)行靜態(tài)切入點:以后每次調(diào)用這個方法時就不需要再執(zhí)行。
讓我們考慮Spring中的一些靜態(tài)切入點實現(xiàn)。
顯而易見,一種描述靜態(tài)切入點的方式是使用正則表達(dá)式。包含Spring在內(nèi)的一些AOP框架都支持這種方式。 org.springframework.aop.support.Perl5RegexpMethodPointcut是一個最基本的正則表達(dá)式切入點, 它使用Perl 5正則表達(dá)式語法。Perl5RegexpMethodPointcut依賴Jakarta ORO進(jìn)行正則表達(dá)式匹配。 Spring也提供了JdkRegexpMethodPointcut類,它使用JDK 1.4或更高版本里提供的正則表達(dá)式支持。
使用Perl5RegexpMethodPointcut類,你可以提供一組模式字符串。 如果其中任意一個模式匹配,切入點將被解析為true。(實際上就是這些切入點的或集。)
下面顯示用法:
<bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
Spring提供了一個方便的類 RegexpMethodPointcutAdvisor, 它也允許我們引用一個通知(記住這里一個通知可以是攔截器,前置通知(before advice),異常通知(throws advice)等類型中的一個)。 在背后,如果使用J2SE 1.4或者以上版本,Spring將使用JdkRegexpMethodPointcut,在之前版本的虛擬機上,Spring將退回去使用Perl5RegexpMethodPointcut。 可以通過設(shè)置perl5屬性為true來強制使用Perl5RegexpMethodPointcut。使用RegexpMethodPointcutAdvisor可以簡化織入,因為一個bean可以同時作為切入點和advisor,如下所示:
<bean id="settersAndAbsquatulateAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="beanNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
RegexpMethodPointcutAdvisor可以和任何通知類型一起使用
動態(tài)切入點比起靜態(tài)切入點在執(zhí)行時要消耗更多的資源。它們同時計算方法參數(shù)和靜態(tài)信息。 這意味著它們必須在每次方法調(diào)用時都被執(zhí)行;由于參數(shù)的不同,評估結(jié)果不能被緩存。
動態(tài)切入點的常見例子是控制流切入點。
Spring控制流切入點在概念上和AspectJ的cflow 切入點很相似, 雖然它的功能不如后者那么強大。(目前還不能讓一個切入點在另外一個切入點所評估的連接點處執(zhí)行)。 一個控制流切入點匹配當(dāng)前的調(diào)用棧。例如,一個連接點被com.mycompany.web包內(nèi)的一個 方法或者SomeCaller類調(diào)用,切入點就可能被激活。 控制流切入點是由org.springframework.aop.support.ControlFlowPointcut 類聲明的。
![]() |
Note |
---|---|
在執(zhí)行時控制流切入點的開銷是非常昂貴的,甚至與其它動態(tài)切入點比起來也是如此。在Java 1.4里,它的開銷差不多是其它動態(tài)切入點的5倍;在Java 1.3中,這個比例甚至達(dá)到10倍。 |
Spring提供了很多有用的切入點基礎(chǔ)類來幫助你實現(xiàn)你自己的切入點。
因為靜態(tài)切入點是最常用的,你可能會像下面那樣繼承StaticMethodMatcherPointcut。這只需要實現(xiàn)一個抽象方法 (雖然也可以覆蓋其它方法來定制行為):
class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } }
動態(tài)切入點也有很多基類。
你可以用Spring 1.0 RC2和更高版本里的自定義切入點及通知類型。
現(xiàn)在讓我們看一下SPring AOP是怎樣處理通知的。
每個通知都是一個Spring bean。一個通知實例既可以被所有被通知的對象共享,也可以被每個被通知對象獨占。 這根據(jù)設(shè)置類共享(per-class)或基于實例(per-instance)的參數(shù)來決定。
類共享通知經(jīng)常會被用到。它很適合用作通用的通知例如事務(wù)advisor。這些advisor不依賴于代理對象的狀態(tài)也不會向代理對象添加新的狀態(tài);它們僅僅在方法和參數(shù)上起作用。
基于實例的通知很適合用作導(dǎo)入器來支持混合類型。在這種情況下,通知向代理對象添加狀態(tài)。
在同一個AOP代理里混合使用類共享和基于實例的通知是可能的。
Spring提供了多種開箱即用的通知類型,而且它們也可以被擴展來支持任何通知類型。讓我們先看看基本概念和標(biāo)準(zhǔn)的通知類型。
在Spring中最基礎(chǔ)的通知類型是攔截around通知。
Spring里使用方法攔截的around通知兼容AOP聯(lián)盟接口。實現(xiàn)around通知的MethodInterceptor應(yīng)當(dāng)實現(xiàn)下面的接口:
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
invoke()方法的MethodInvocation參數(shù)暴露了被調(diào)用的方法; 目標(biāo)連接點;AOP代理以及傳遞給方法的參數(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)用。這個方法 繼續(xù)運行指向連接點的攔截器鏈并返回proceed()的結(jié)果。然而,一個類似任何環(huán)繞通知的MethodInterceptor, 可以返回一個不同的值或者拋出一個異常而不是調(diào)用proceed方法。但除非你有很好的理由,否則不要考慮這樣做!
![]() |
Note |
---|---|
MethodInterceptor提供了與其它AOP聯(lián)盟兼容實現(xiàn)的互操作性。本節(jié)的剩下部分將討論其它的通知類型,它們實現(xiàn)了通用的AOP概念, 但是以一種Spring風(fēng)格的方式來實現(xiàn)的。使用最通用的通知類型還有一個好處,固定使用MethodInterceptor 環(huán)繞通知可以讓你在其它的AOP框架里 運行你所定制的方面。要注意現(xiàn)在切入點還不能和其它框架進(jìn)行互操作,AOP聯(lián)盟目前還沒有定義切入點接口。 |
一個更簡單的通知類型是before 通知。它不需要 MethodInvocation對象,因為它只是在進(jìn)入方法之前被調(diào)用。
前置通知(before advice)的一個主要優(yōu)點是它不需要調(diào)用proceed() 方法,因此就不會發(fā)生 無意間運行攔截器鏈?zhǔn)〉那闆r。
MethodBeforeAdvice 接口被顯示在下面。(Spring的API設(shè)計能夠為類中的成員變量提供前置通知,雖然這可以把通用對象應(yīng)用到成員變量攔截上,但看起來Spring并不打算實現(xiàn)這個功能。)
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; }
注意返回值的類型是void。前置通知可以在連接點執(zhí)行之前插入自定義行為,但是不能修改連接點的返回值。如果一個前置通知拋出異常,這將中止攔截器鏈的進(jìn)一步執(zhí)行。 異常將沿著攔截器鏈向回傳播。如果異常是非強制檢查的(unchecked)或者已經(jīng)被包含在被調(diào)用方法的簽名中(譯者:即出現(xiàn)在方法聲明的throws子句中),它將被直接返回給客戶端; 否則它將由AOP代理包裝在一個非強制檢查異常中返回。
這里是Spring里一個前置通知的例子,它計算所有方法被調(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; } }
![]() |
Tip |
---|---|
前置通知可以和任何切入點一起使用。 |
如果連接點拋出異常,異常通知(throws advice)將在連接點返回后被調(diào)用。 Spring提供類型檢查的異常通知,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法:它只是一個標(biāo)記接口用來標(biāo)識 所給對象實現(xiàn)了一個或者多個針對特定類型的異常通知方法。這些方法應(yīng)當(dāng)滿足下面的格式
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一個參數(shù)是必須的。因此異常通知方法對方法及參數(shù)的需求,方法的簽名將從一到四個參數(shù)之間變化。 下面是一些throws通知的例子。
當(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 will 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 will all arguments } }
![]() |
Tip |
---|---|
異常通知可以和任何切入點一起使用。 |
Spring中的一個后置通知(After Returning advice)必須實現(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)對象。
下面的后置通知計算所有運行成功(沒有拋出異常)的方法調(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é)果。
![]() |
Tip |
---|---|
后置通知可以和任何切入點一起使用。 |
Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進(jìn)行處理。
引入通知需要一個IntroductionAdvisor, 和一個IntroductionInterceptor, 后者實現(xiàn)下面的接口:
public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); }
invoke() 方法,繼承了AOP聯(lián)盟MethodInterceptor 接口,必須確保實現(xiàn)引入: 這里的意思是說,如果被調(diào)用的方法位于一個已經(jīng)被引入接口里,這個引入攔截器將負(fù)責(zé)完成對這個方法的調(diào)用--因為后者不能調(diào)用proceed()方法。
引入通知不能和任何切入點一起使用,因為它是應(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()方法返回這個advisor所引入的接口。
validateInterfaces()方法將被內(nèi)部使用來查看被引入的接口是否能夠由配置的IntroductionInterceptor來實現(xiàn)。
讓我們看看從Spring測試集里拿來的一個簡單例子。讓我們假設(shè)我們希望把下面的接口引入給一個或者多個對象:
public interface Lockable { void lock(); void unlock(); boolean locked(); }
這里描述了一個混合類型。我們希望不論原本對象是什么類型,都把這個被通知對象轉(zhuǎn)換為Lockable接口并可以調(diào)用lock 和unlock 方法。 如果我們調(diào)用lock() 方法,我們希望所有的setter 方法拋出一個LockedException異常。這樣我們就可以加入一個方面來確保對象在得到通知之前是不可修改的:一個關(guān)于AOP的好例子。
首先,我們需要一個IntroductionInterceptor來做粗活。這里,我們擴展了 org.springframework.aop.support.DelegatingIntroductionInterceptor這個方便的類。我們能夠直接實現(xiàn) IntroductionInterceptor接口,但在這個例子里使用DelegatingIntroductionInterceptor是最好的選擇。
DelegatingIntroductionInterceptor設(shè)計為把一個引入托管給一個實現(xiàn)這個接口的類, 這通過隱藏攔截的使用來實現(xiàn)。托管可以被設(shè)置到任何具有構(gòu)造器方法的類;這里使用缺省托管(即使用無參構(gòu)造器)。 因此在下面這個例子里,托管者將是DelegatingIntroductionInterceptor的子類 LockMixin。 當(dāng)一個托管實現(xiàn)被提供,DelegatingIntroductionInterceptor實例將查找托管所實現(xiàn)的所有接口 (除了IntroductionInterceptor之外),并為這些接口的介紹提供支持。子類例如LockMixin 可以調(diào)用suppressInterface(Class intf) 方法來禁止那些不應(yīng)該被暴露的接口。 然而,不論IntroductionInterceptor支持多少接口, IntroductionAdvisor的使用將控制哪些接口真正被暴露。 一個被引入的接口將覆蓋目標(biāo)對象實現(xiàn)的相同接口.
這樣LockMixin就繼承了DelegatingIntroductionInterceptor并實現(xiàn)了Lockable 接口本身。 這里父類會自動選擇Lockable接口并提供引入支持,因此我們不需要配置它。用這種方法我們能夠介紹任意數(shù)量的接口。
注意locked實例變量的用法。這有效地向目標(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)包含了一個實現(xiàn)--如果一個方法被引入,這個實現(xiàn)將調(diào)用實際的托管方法,否則它將直接處理連接點--通常這已經(jīng)足夠了。在當(dāng)前這個例子里,我們需要增加一個檢查:如果處于加鎖(locked)狀態(tài),沒有setter方法可以被調(diào)用。
引入處理器的要求是很簡單的。它的全部要求只是保持一個特定的LockMixin實例, 并說明被通知的接口--在這個例子里,只有一個Lockable接口。 一個更復(fù)雜的例子也許會獲取一個介紹攔截器的引用(后者可以被定義為一個prototype): 在這種情況下,不需要對LockMixin進(jìn)行相關(guān)配置,因此我們可以簡單的用new關(guān)鍵字來創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } }
我們可以很容易應(yīng)用這個advisor:它不需要配置。(然而,下面是必須記住的:不可以在沒有IntroductionAdvisor的情況下使用IntroductionInterceptor。) 對于通常的引入advisor必須是基于實例的,因為它是有狀態(tài)的。因此,對于每個被通知對象我們需要一個不同 實例的LockMixinAdvisor和LockMixin。這種情況下advisor保存了被通知對象的部分狀態(tài)。
我們能夠通過使用Advised.addAdvisor() 的編程方式來應(yīng)用advisor,或者像其它advisor那樣(也是推薦的方式)在XML里進(jìn)行配置。全部的代理創(chuàng)建選擇(包括“自動代理創(chuàng)建器”)將在下面進(jìn)行討論, 看看如何正確地處理introduction和有狀態(tài)混合類型。
在Spring里,一個advisor是一個僅僅包含一個通知對象和與之關(guān)聯(lián)的切入點表達(dá)式的切面。
除了引入這種特殊形式,任何advisor都可以和任何通知一起工作。 org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor類。例如,它可以和: MethodInterceptor,BeforeAdvice 或者 ThrowsAdvice一起使用。
在Spring里有可能在同一個AOP代理里模糊advisor和通知類型。例如,你可以在一個代理配置里使用一個interception環(huán)繞通知,一個異常通知和一個前置通知:Spring將負(fù)責(zé)自動創(chuàng)建所需的攔截器鏈。
如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來管理你的業(yè)務(wù)對象--這正是你應(yīng)該做的--你也許會想要使用Spring中關(guān)于AOP的FactoryBean。(記住使用工廠bean引入一個間接層之后,我們就可以創(chuàng)建不同類型的對象了)。
![]() |
Note |
---|---|
Spring 2.0的AOP支持也在底層使用工廠bean。 |
在Spring里創(chuàng)建一個AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個類對應(yīng)用的切入點和通知提供了完整的控制能力(包括它們的應(yīng)用順序)。然而如果你不需要這種控制,你會喜歡更簡單的方式。
像其它的FactoryBean實現(xiàn)一樣,ProxyFactoryBean引入了一個間接層。如果你定義一個名為foo的ProxyFactoryBean, 引用foo的對象看到的將不是ProxyFactoryBean實例本身,而是一個ProxyFactoryBean實現(xiàn)里getObject() 方法所創(chuàng)建的對象。 這個方法將創(chuàng)建一個AOP代理,它包裝了一個目標(biāo)對象。
使用ProxyFactoryBean或者其它IoC相關(guān)類帶來的最重要的好處之一就是創(chuàng)建AOP代理,這意味著通知和切入點也可以由IoC來管理。這是一個強大的功能并使得某些特定的解決方案成為可能, 而這些用其它AOP框架很難做到。例如,一個通知也許本身也要引用應(yīng)用程序?qū)ο螅ú粌H僅是其它AOP框架中也可以訪問的目標(biāo)對象),這令你可以從依賴注射的可拔插特性中獲益。
通常情況下Spring提供了大多數(shù)的FactoryBean實現(xiàn),ProxyFactoryBean類本身也是一個JavaBean。它的屬性被用來:
-
指定你希望代理的目標(biāo)對象
-
指定是否使用CGLIB(查看下面叫做Section 7.5.3, “基于JDK和CGLIB的代理”的小節(jié))。
一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(這個類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:
-
proxyTargetClass:這個屬性為true時,目標(biāo)類本身被代理而不是目標(biāo)類的接口。如果這個屬性值被設(shè)為true,CGLIB代理將被創(chuàng)建(可以參看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。
-
optimize:用來控制通過CGLIB創(chuàng)建的代理是否使用激進(jìn)的優(yōu)化策略。除非完全了解AOP代理如何處理優(yōu)化,否則不推薦用戶使用這個設(shè)置。目前這個屬性僅用于CGLIB代理;對于JDK動態(tài)代理(缺省代理)無效。
-
frozen:用來控制代理工廠被配置之后,是否還允許修改通知。缺省值為false(即在代理被配置之后,不允許修改代理的配置)。
-
exposeProxy:決定當(dāng)前代理是否被保存在一個ThreadLocal中以便被目標(biāo)對象訪問。(目標(biāo)對象本身可以通過MethodInvocation來訪問,因此不需要ThreadLocal。) 如果個目標(biāo)對象需要獲取代理而且exposeProxy屬性被設(shè)為true,目標(biāo)對象可以使用AopContext.currentProxy()方法。
-
aopProxyFactory:使用AopProxyFactory的實現(xiàn)。這提供了一種方法來自定義是否使用動態(tài)代理,CGLIB或其它代理策略。 缺省實現(xiàn)將根據(jù)情況選擇動態(tài)代理或者CGLIB。一般情況下應(yīng)該沒有使用這個屬性的需要;它是被設(shè)計來在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean中需要說明的其它屬性包括:
-
proxyInterfaces:需要代理的接口名的字符串?dāng)?shù)組。如果沒有提供,將為目標(biāo)類使用一個CGLIB代理(也可以查看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。
-
interceptorNames:Advisor的字符串?dāng)?shù)組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優(yōu)先服務(wù)。就是說列表里的第一個攔截器將能夠第一個攔截調(diào)用。
這里的名字是當(dāng)前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因為這會導(dǎo)致ProxyFactoryBean忽略通知的單例設(shè)置。
你可以把一個攔截器的名字加上一個星號作為后綴(*)。這將導(dǎo)致這個應(yīng)用程序里所有名字以星號之前部分開頭的advisor都被應(yīng)用。你可以在Section 7.5.6, “使用“全局”advisor” 發(fā)現(xiàn)一個使用這個特性的例子。
-
單例:工廠是否應(yīng)該返回同一個對象,不論方法getObject()被調(diào)用的多頻繁。多個FactoryBean實現(xiàn)都提供了這個方法。缺省值是true。如果你希望使用有狀態(tài)的通知--例如,有狀態(tài)的mixin--可以把單例屬性的值設(shè)置為false來使用原型通知。
這個小節(jié)作為說明性文檔,解釋了對于一個目標(biāo)對象(需要被代理的對象),ProxyFactryBean是如何決定究竟創(chuàng)建一個基于JDK還是CGLIB的代理的。
![]() |
Note |
---|---|
ProxyFactoryBean需要創(chuàng)建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。現(xiàn)在ProxyFactoryBean在關(guān)于自動檢測接口方面使用了與TransactionProxyFactoryBean相似的語義。 |
如果一個需要被代理的目標(biāo)對象的類(后面將簡單地稱它為目標(biāo)類)沒有實現(xiàn)任何接口,那么一個基于CGLIB的代理將被創(chuàng)建。這是最簡單的場景,因為JDK代理是基于接口的,沒有接口意味著沒有使用JDK進(jìn)行代理的可能。 在目標(biāo)bean里將被插入探測代碼,通過interceptorNames屬性給出了攔截器的列表。注意一個基于CGLIB的代理將被創(chuàng)建即使ProxyFactoryBean的proxyTargetClass屬性被設(shè)置為false。 (很明顯這種情況下對這個屬性進(jìn)行設(shè)置是沒有意義的,最好把它從bean的定義中移除,因為雖然這只是個多余的屬性,但在許多情況下會引起混淆。)
如果目標(biāo)類實現(xiàn)了一個(或者更多)接口,那么創(chuàng)建代理的類型將根據(jù)ProxyFactoryBean的配置來決定。
如果ProxyFactoryBean的proxyTargetClass屬性被設(shè)為true,那么一個基于CGLIB的代理將創(chuàng)建。這樣的規(guī)定是有意義的,遵循了最小驚訝法則(保證了設(shè)定的一致性)。 甚至當(dāng)ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個或者多個全限定接口名,而proxyTargetClass屬性被設(shè)置為true仍然將實際使用基于CGLIB的代理。
如果ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個或者多個全限定接口名,一個基于JDK的代理將被創(chuàng)建。被創(chuàng)建的代理將實現(xiàn)所有在proxyInterfaces屬性里被說明的接口;如果目標(biāo)類實現(xiàn)了全部在proxyInterfaces屬性里說明的接口以及一些額外接口,返回的代理將只實現(xiàn)說明的接口而不會實現(xiàn)那些額外接口。
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設(shè)置,但是目標(biāo)類實現(xiàn)了一個(或者更多)接口,那么ProxyFactoryBean將自動檢測到這個目標(biāo)類已經(jīng)實現(xiàn)了至少一個接口, 一個基于JDK的代理將被創(chuàng)建。被實際代理的接口將是目標(biāo)類所實現(xiàn)的全部接口;實際上,這和在proxyInterfaces屬性中列出目標(biāo)類實現(xiàn)的每個接口的情況是一樣的。然而,這將顯著地減少工作量以及輸入錯誤的可能性。
讓我們看一個關(guān)于ProxyFactoryBean的簡單例子。這個例子涉及:
-
一個將被代理的目標(biāo)bean。在下面的例子里這個bean是“personTarget”。
-
被用來提供通知的一個advisor和一個攔截器。
-
一個AOP代理bean的定義,它說明了目標(biāo)對象(personTarget bean)以及需要代理的接口,還包括需要被應(yīng)用的通知。
<bean id="personTarget" class="com.mycompany.PersonImpl"> <property name="name"><value>Tony</value></property> <property name="age"><value>51</value></property> </bean> <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty"><value>Custom string property value</value></property> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> <property name="target"><ref local="personTarget"/></property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
注意interceptorNames屬性接受一組字符串:當(dāng)前工廠中攔截器或advisorbean的名字。攔截器,advisor,前置, 后置和異常通知對象都可以在這里被使用。這里advisor的順序是很重要的。
![]() |
Note |
---|---|
你也許很奇怪為什么這個列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設(shè)置為false,它必須返回獨立的代理實例。如果任何advisor本身是一個原型,則每次都返回一個獨立實例,因此它必須能夠從工廠里獲得原型的一個實例;保存一個引用是不夠的。 |
上面“person” bean的定義可以被用來取代一個Person接口的實現(xiàn),就像下面這樣:
Person person = (Person) factory.getBean("person");
在同一個IoC上下文中其它的bean可以對這個bean有基于類型的依賴,就像對一個普通的Java對象那樣:
<bean id="personUser" class="com.mycompany.PersonUser"> <property name="person"><ref local="person" /></property> </bean>
這個例子里的PersonUser類將暴露一個類型為Person的屬性。就像我們關(guān)心的那樣,AOP代理可以透明地取代一個“真實”的person接口實現(xiàn)。然而,它的類將是一個動態(tài)代理類。 它可以被轉(zhuǎn)型成Advised接口(將在下面討論)。
就像下面這樣,你可以使用一個匿名內(nèi)部bean來隱藏目標(biāo)和代理之間的區(qū)別。僅僅ProxyFactoryBean的定義有所不同;通知的定義只是由于完整性的原因而被包括進(jìn)來:
<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty"><value>Custom string property value</value></property> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="person" class="org.springframework.aop.framework.ProxyFactor Bean"> <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> <!-- Use inner bean, not local reference to target --> <property name="target"> <bean class="com.mycompany.PersonImpl"> <property name="name"><value>Tony</value></property> <property name="age"><value>51</value></property> </bean> </property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
對于只需要一個Person類型對象的情況,這是有好處的:如果你希望阻止應(yīng)用程序上下文的用戶獲取一個指向未通知對象的引用或者希望避免使用Spring IoC 自動織入 時的混淆。 按理說ProxyFactoryBean定義還有一個優(yōu)點是它是自包含的。然而,有時能夠從工廠里獲取未通知的目標(biāo)也是一個優(yōu)點:例如,在某些測試場景里。
如果你需要代理一個類而不是代理一個或是更多接口,那么情況將是怎樣?
想象在我們上面的例子里,不存在Person接口:我們需要通知一個叫做Person的類,它沒有實現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動態(tài)代理。 這只需簡單地把上面ProxyFactoryBean的proxyTargetClass屬性設(shè)為true。雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時,通知沒有實現(xiàn)接口的類的能力是非常有用的。(通常情況下,Spring沒有任何規(guī)定。它只是讓你很容易根據(jù)實際情況選擇最好的解決方案,避免強迫使用特定方式)。
也許你希望你能夠在任何情況下都強制使用CGLIB,甚至在你使用接口的時候也這樣做。
CGLIB通過在運行時生成一個目標(biāo)類的子類來進(jìn)行代理工作。Spring配置這個生成的子類對原始目標(biāo)對象的方法調(diào)用進(jìn)行托管:子類實現(xiàn)了裝飾器(Decorator)模式,把通知織入。
CGLIB的代理活動應(yīng)當(dāng)對用戶是透明的。然而,有一些問題需要被考慮:
-
Final方法不可以被通知,因為它們不能被覆蓋。
-
你需要在你的類路徑里有CGLIB 2的庫;使用動態(tài)代理的話只需要JDK。
在CGLIB代理和動態(tài)代理之間的速度差別是很小的。在Spring 1.0中,動態(tài)代理會快一點點。但這點可能在將來被改變。這種情況下,選擇使用何種代理時速度不應(yīng)該成為決定性的理由。
通過在一個攔截器名后添加一個星號,所有bean名字與星號之前部分相匹配的通知都將被加入到advisor鏈中。這讓你很容易添加一組標(biāo)準(zhǔn)的“全局”advisor:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>globa *</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
你也許需要許多相似的代理定義,特別是定義事務(wù)性代理的時候。使用父子bean定義,以及內(nèi)部bean定義,可以讓代理定義大大得到極大的簡化。
首先從父bean開始,為代理bean創(chuàng)建bean定義模版:
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
這個bean本身將永遠(yuǎn)不會被初始化,所以實際上是不完整的。而后每個需要創(chuàng)建的代理都是這個bean定義的子bean定義,它們把代理的目標(biāo)類包裝為一個內(nèi)部bean定義,因為目標(biāo)對象本身將不會被單獨使用。
<bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean>
當(dāng)然你可以覆蓋從模版中繼承的屬性,例如在下面這個例子里的事務(wù)傳播設(shè)置:
<bean id="mySpecialService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MySpecialServiceImpl"> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="store*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
要注意上面例子中我們已經(jīng)明確地通過設(shè)定abstract屬性把父bean定義標(biāo)注為abstract,在前面的章節(jié)里有描述,這樣它實際上不能被初始化。 缺省情況下應(yīng)用程序上下文(不僅僅是bean工廠)將預(yù)先初始化所有的實例為單例。因此下面這點是很重要的(至少對于單例bean來說),如果你有一個(父)bean定義你希望僅僅作為模版使用,而這個定義說明了一個類,你必須把abstract參數(shù)設(shè)置為true,否則應(yīng)用程序上下文將試圖預(yù)先初始化它。
使用Spring通過編程創(chuàng)建AOP代理是很容易的。這使你可以使用Spring AOP而不必依賴于Spring IoC。
下面的清單顯示了如何使用一個攔截器和一個advisor來為一個目標(biāo)對象來創(chuàng)建一個代理。目標(biāo)對象實現(xiàn)的接口將被自動代理:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addInterceptor(myMethodInterc ptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
第一步是創(chuàng)建一個類型為org.springframework.aop.framework.ProxyFactory的對象。你可以像上面例子里那樣使用一個目標(biāo)對象來創(chuàng)建它,或者在一個可選的構(gòu)造器里說明需要被代理的接口。
你可以添加攔截器或advisor,并在ProxyFactory的生命周期里操作它們。如果你加入一個IntroductionInterceptionAroundAdvisor,你可以讓代理實現(xiàn)額外的接口。
在ProxyFactory里也有很方便的方法(繼承自AdvisedSupport)允許你加入其它的通知類型例如前置和異常通知。AdvisedSupport是ProxyFactory 和ProxyFactoryBean的共同父類。
![]() |
Tip |
---|---|
在大多數(shù)應(yīng)用程序里,把AOP代理的創(chuàng)建和IoC框架集成是最佳實踐。通常情況下我們推薦你在Java代碼外進(jìn)行AOP的配置。 |
在創(chuàng)建了AOP代理之后,你能夠使用org.springframework.aop.framework.Advised接口對它們進(jìn)行管理。任何AOP代理都能夠被轉(zhuǎn)型為這個接口,不論它實現(xiàn)了哪些其它接口。這個接口包括下面的方法:
Advisor[] getAdvisors(); void addAdvice(Advice advice) throws AopConfigException; void addAdvice(int pos, Advice advice) throws AopConfigException; void addAdvisor(Advisor advisor) throws AopConfigException; void addAdvisor(int pos, Advisor advisor) throws AopConfigException; int indexOf(Advisor advisor); boolean removeAdvisor(Advisor advisor) throws AopConfigException; void removeAdvisor(int index) throws AopConfigException; boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; boolean isFrozen();
getAdvisors()方法將為每個已經(jīng)被加入工廠的advisor,攔截器或者其它通知類型返回一個advisor。如果你曾經(jīng)添加一個advisor,那么所返回的advisor將是你加入的對象。 如果你曾經(jīng)加入一個攔截器或者其它通知類型,Spring將把它們包裝在一個advisor里,后者使用一個永遠(yuǎn)返回true的切入點。因此如果你曾經(jīng)加入一個MethodInterceptor, 返回的advisor將是一個DefaultPointcutAdvisor,它可以返回你加入的MethodInterceptor和一個匹配所有類和方法的切入點。
addAdvisor()方法可以用來添加任何advisor。通常保存切入點和通知的advisor是DefaultPointcutAdvisor,它可以用于任何通知或切入點(但不包括引入類型)。
缺省情況下,你可以加入或移除advisor或者攔截器甚至當(dāng)代理已經(jīng)被創(chuàng)建之后。唯一的限制是無法加入或者移除一個引入advisor,因為工廠中獲得的已有代理不能顯示接口的改變(你可以通過從工廠里獲取一個新的代理來避免這個問題)。
下面是一個簡單的例子,它把一個AOP代理轉(zhuǎn)型為Advised接口,檢查并操作它的通知:
Advised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); int oldAdvisorCount = advisors.length; System.out.println(oldAdvisorCount + " advisors"); // Add an advice like an interceptor without a pointcut // Will match all proxied methods // Can use for interceptors, before, after returning or throws advice advised.addAdvice(new DebugInterceptor()); // Add selective advice us ng a pointcut advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
![]() |
Note |
---|---|
在一個實際運行的系統(tǒng)里,修改一個業(yè)務(wù)對象上的通知是否明智是個問題,雖然無疑在某些情況下這樣做是合理的。然而這在開發(fā)中是很有用的: 例如,在測試的時候。對于希望測試的方法調(diào)用,有時我發(fā)現(xiàn)把測試代碼加入到一個攔截器或者其它通知里是非常有用的。(例如,通知可以與目標(biāo)方法存在于同一個事務(wù)里,在把事務(wù)標(biāo)記為回滾之前可以用SQL來檢查數(shù)據(jù)庫是否被正確的更新了。) |
依賴于你怎樣創(chuàng)建代理,你通常可以設(shè)置一個 frozen標(biāo)志,在這種情況下 Advised的isFrozen()方法將返回true,任何增加或者移除通知的修改都會導(dǎo)致一個AopConfigException異常。 在某些情況下這種凍結(jié)被通知對象狀態(tài)的能力是很有用的:例如,防止調(diào)用代碼來移除一個進(jìn)行安全檢查的攔截器。在Spring 1.1中它也被用來允許激進(jìn)優(yōu)化,如果已經(jīng)知道不需要運行時對通知進(jìn)行修改的話。
到目前為止我們已經(jīng)考慮了如何使用ProxyFactoryBean或者類似的工廠bean來顯式創(chuàng)建AOP代理。
Spring也允許我們使用“自動代理”的bean定義,可以自動對被選中的bean定義進(jìn)行代理。這建立在Spring的“bean post processor”功能上,后者允許在容器加載時修改任何bean的定義。
在這個模型下,你在你的XML bean定義文件中建立一些特定的bean定義來配置自動代理功能。這允許你僅僅聲明那些將被自動代理的適當(dāng)目標(biāo):你不需要使用ProxyFactoryBean。
有兩種方式可以做到這點:
-
使用一個引用當(dāng)前上下文中特定bean的自動代理創(chuàng)建器。
-
一個專用自動代理的創(chuàng)建需要被單獨考慮;自動代理創(chuàng)建由源代碼級別的元數(shù)據(jù)屬性驅(qū)動。
org.springframework.aop.framework.autoproxy包提供了下列標(biāo)準(zhǔn)自動代理創(chuàng)建器。
BeanNameAutoProxyCreator為名字匹配字符串或者通配符的bean自動創(chuàng)建AOP代理。
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"><value>jdk*,onlyJdk</value></property> <property name="interceptorNames"> <list> <value>myInterceptor</value> </list> </property> </bean>
和ProxyFactoryBean一樣,這里有一個interceptorNames屬性而不是一個攔截器的列表,這允許使用原型(prototype)advisor。這里的“攔截器”可以是advisor或任何通知類型。
與通常的自動代理一樣,使用BeanNameAutoProxyCreator的主要目的是把相同的配置一致地應(yīng)用到多個對象,并且使用最少量的配置。一個流行的選擇是把聲明式事務(wù)應(yīng)用到多個對象上。
那些名字匹配的Bean定義,例如上面的例子里的“jdkMyBean”和“onlyJdk”,本身只是目標(biāo)類的普通bean定義。一個AOP對象將被BeanNameAutoProxyCreator自動創(chuàng)建。 相同的通知將被應(yīng)用到全部匹配的bean上。注意如果advisor被使用(而不是像上面例子里那樣使用攔截器),對于不同bean可以應(yīng)用不同的切入點。
一個更加通用而且強大得多的自動代理創(chuàng)建器是DefaultAdvisorAutoProxyCreator。它自動應(yīng)用當(dāng)前上下文中適當(dāng)?shù)腶dvisor,無需在自動代理advisor的bean定義中包括bean的名字。 比起BeanNameAutoProxyCreator,它提供了同樣關(guān)于一致性配置的優(yōu)點而避免了前者的重復(fù)性。
使用這個功能將涉及:
-
說明一個 DefaultAdvisorAutoProxyCreator的bean定義
-
在同一個或者相關(guān)的上下文中說明任意數(shù)量的advisor。注意這些必須是advisor而不僅僅是攔截器或者其它通知。這點是必要的因為必須有一個切入點被評估,以便檢查每個通知候選bean定義的合適性。
DefaultAdvisorAutoProxyCreator將自動評估包括在每個advisor中的切入點,來看看它應(yīng)當(dāng)應(yīng)用哪個(如果有的話)通知到每個業(yè)務(wù)對象(例如例子里的“businessObject1”和“businessObject2”)。
這意味著可以向每個業(yè)務(wù)對象應(yīng)用任意數(shù)量的advisor。對于一個業(yè)務(wù)對象,如果沒有任何advisor中的切入點匹配它的任何方法,這個對象將不會被代理。當(dāng)為新的業(yè)務(wù)對象加入bean定義時,如果有必要它們將自動被代理。
通常自動代理的好處是它讓調(diào)用者或者被依賴對象不能得到一個沒有通知過的對象。在這個ApplicationContext上調(diào)用getBean("businessObject1")將返回一個AOP代理,而不是目標(biāo)業(yè)務(wù)對象。(前面顯示的“內(nèi)部bean”也提供了同樣的優(yōu)點。)
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> <bean id="businessObject1" class="com.mycompany.BusinessObject1"> <!-- Properties omitted --> </bean> <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
如果你想要把相同的通知一致性地應(yīng)用到許多業(yè)務(wù)對象上,DefaultAdvisorAutoProxyCreator是非常有用的。一旦框架的定義已經(jīng)完成,你可以簡單地加入新的業(yè)務(wù)對象而不必包括特定的代理配置。你也可以很容易的去掉額外的切面--例如,跟蹤或者性能監(jiān)視切面--僅僅對配置作很小的修改。
DefaultAdvisorAutoProxyCreator支持過濾(通過使用一個命名約定讓只有特定的advisor被評估,允許在同一個工廠里使用多個不同配置的AdvisorAutoProxyCreator)和排序。advisor可以實現(xiàn)org.springframework.core.Ordered接口來確保以正確的順序被應(yīng)用。 上面例子里的TransactionAttributeSourceAdvisor 有一個可配置的序號值;缺省情況下是沒有排序的。
一個非常重要的自動代理類型是由元數(shù)據(jù)驅(qū)動的。這提供了一種和.NET ServicedComponents相似的編程模型。作為使用類似EJB里的XML描述符的替代,對于事務(wù)管理和其它企業(yè)服務(wù)的配置都將被保存在源代碼級別的屬性里。
在這個情況下,你使用DefaultAdvisorAutoProxyCreator和可以理解元數(shù)據(jù)屬性的advisor。元數(shù)據(jù)被保存在候選advisor里的切入點部分,而不是在自動代理創(chuàng)建類本身。
這是一個DefaultAdvisorAutoProxyCreator的特殊例子,它本身沒有什么特別。(元數(shù)據(jù)的相關(guān)代碼保存在advisor內(nèi)的切入點里,而不是AOP框架本身)。
JPetStore示例應(yīng)用程序的/attributes 目錄顯示了如何使用參數(shù)驅(qū)動的自動代理。在這個例子里,不需要使用TransactionProxyFactoryBean。因為使用了元數(shù)據(jù)相關(guān)的切入點, 所以簡單在業(yè)務(wù)對象上定義事務(wù)屬性就足夠了。在/WEB-INF/declarativeServices.xml里的bean定義包括了下面的片斷,注意這是通用的,可以被用在JPetStore以外的地方:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> <property name="attributes" ref="attributes"/> </bean> </property> </bean> <bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>
DefaultAdvisorAutoProxyCreator bean定義(名字是不重要的,因此甚至可以在定義里省略它)將在當(dāng)前應(yīng)用程序上下文中查找所有合適的切入點。在這個例子里,TransactionAttributeSourceAdvisor類型的“transactionAdvisor”bean定義將應(yīng)用到帶有一個事務(wù)屬性的類或方法上。 TransactionAttributeSourceAdvisor的構(gòu)造器依賴于一個TransactionInterceptor。這個例子里通過自動織入解決了這個問題。AttributesTransactionAttributeSource依賴于一個org.springframework.metadata.Attributes接口的實現(xiàn)。 在這個代碼片斷里,“attributes”bean使用Jakarta Commons Attributes API來獲取屬性信息以滿足這個要求。(應(yīng)用程序代碼必須已經(jīng)使用Commons Attribut來es的編譯任務(wù)編譯過了。)
JPetStore示例應(yīng)用程序的 /annotation 目錄包括了一個由JDK 1.5+注解驅(qū)動的自動代理的模擬例子。下面的配置允許自動檢測Spring的Transactional注解,這可以為包含注解的bean提供隱式代理:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> </property> </bean>
這里定義的TransactionInterceptor依賴于一個PlatformTransactionManager定義,后者沒有被包括在這個通用的文件里(雖然它可以被包括在這里)因為它在應(yīng)用程序的事務(wù)需求規(guī)范中指定(在這個例子里使用JTA,而在其它情況下,可以是Hibernate,JDO或者JDBC):
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
![]() |
Tip |
---|---|
如果你只需要聲明式事務(wù)管理,使用這些通用的XML定義將導(dǎo)致Spring自動代理所有帶有事務(wù)屬性的類或者方法。你將不需要直接使用AOP工作, 這個編程模型和.NET的ServicedComponents相似。 |
這個架構(gòu)是可以擴展的。可以在自定義屬性的基礎(chǔ)上進(jìn)行自動代理。你所需要做的是:
-
定義你自己的自定義屬性
-
使用必要的通知說明一個advisor,也包括一個切入點,后者可以被類或者方法上的自定義屬性觸發(fā)。你也許能夠使用已有的通知,而僅僅實現(xiàn)一個能夠處理自定義屬性的靜態(tài)切入點。
可以讓這些advisor對于每個被通知對象(例如,mixins)都是唯一的:僅僅需要在bean定義中被定義為原型而不是單例。例如,在上面所顯示的Spring測試集中的LockMixin引入攔截器可以和一個屬性驅(qū)動的切入點聯(lián)合定位一個mixin, 像這里顯示的這樣。我們使用通用的DefaultPointcutAdvisor,使用JavaBean屬性進(jìn)行配置:
<bean id="lockMixin" class="org.springframework.aop.LockMixin" scope="prototype"/> <bean id="lockableAdvisor" c ass="org.springframework.aop.support.DefaultPointcutAdvisor" scope="prototype"/> <property name="pointcut" ref="myAttributeAwarePointcut"/> <property name="advice" ref="lockMixin"/> </bean> <bean id="anyBean" class="anyclass" ...
如果參數(shù)相關(guān)的切入點匹配anyBean或其它bean定義里的任何方法,mixin將被應(yīng)用。注意lockMixin和lockableAdvisor的定義都是原型。myAttributeAwarePointcut切入點可以是個單例,因為它沒有為單個被通知對象保持狀態(tài)。
Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource接口進(jìn)行描述。這個接口負(fù)責(zé)返回一個實現(xiàn)連接點的“目標(biāo)對象(target object)”。每當(dāng)AOP代理處理一個方法調(diào)用時都會向TargetSource的實現(xiàn)請求一個目標(biāo)實例。
使用Spring AOP的開發(fā)者通常不需要直接和TargetSource打交道,但這提供了一種強大的方式來支持池化(pooling),熱交換(hot swappable)和其它高級目標(biāo)。例如,一個使用池來管理實例的TargetSource可以為每個調(diào)用返回一個不同的目標(biāo)實例。
如果你不指定一個TargetSource,一個缺省實現(xiàn)將被使用,它包裝一個本地對象。對于每次調(diào)用它將返回相同的目標(biāo)(像你期望的那樣)。
讓我們看看Spring提供的標(biāo)準(zhǔn)目標(biāo)源(target source)以及如何使用它們。
![]() |
Tip |
---|---|
當(dāng)使用一個自定義的目標(biāo)源,你的目標(biāo)通常需要是一個原型而不是一個單例的bean定義。這允許Spring在必要時創(chuàng)建新的目標(biāo)實例。 |
org.springframework.aop.target.HotSwappableTargetSource允許當(dāng)調(diào)用者保持引用的時候,切換一個AOP代理的目標(biāo)。
修改目標(biāo)源的目標(biāo)將立即生效。 HotSwappableTargetSource是線程安全的。
你可以通過HotSwappableTargetSource的 swap()方法來改變目標(biāo),就像下面那樣:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget);
所需的XML定義看起來像下面這樣:
<bean id="initialTarget" class="mycompany.OldTarget"/> <bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource"> <constructor-arg ref="initialTarget"/> </bean> <bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="swapper"/> </bean>
上面的swap()調(diào)用修改了swappable bean的目標(biāo)。保持對這個bean的引用的客戶將不知道發(fā)生了這個修改,但是將可以立即點擊新的目標(biāo)。
這個例子沒有添加任何通知--也不必為使用一個TargetSource添加任何通知--當(dāng)然任何TargetSource都可以與任意通知聯(lián)合使用。
使用一個池化目標(biāo)源提供了和無狀態(tài)session EJB類似的編程模型,它維護(hù)一個包括相同實例的池,方法調(diào)用結(jié)束后將把對象釋放回池中。
Spring池化和SLSB池化之間的一個決定性區(qū)別是Spring池化功能可以用于任何POJO。就像Spring通常情況下那樣,這個服務(wù)是非侵入式的。
Spring對Jakarta Commons Pool 1.3提供了開箱即用的支持,后者提供了一個相當(dāng)有效的池化實現(xiàn)。要使用這個特性,你需要在應(yīng)用程序路徑中存在commons-pool的Jar文件。 也可以通過繼承org.springframework.aop.target.AbstractPoolingTargetSource來支持其它的池化API。
下面是示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype"> ... properties omitted </bean> <bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> <property name="maxSize" value="25"/> </bean> <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="poolTargetSource"/> <property name="interceptorNames" value="myInterceptor"/> </bean>
注意目標(biāo)對象--例子里的“businessObjectTarget”--必須是個原型。這允許PoolingTargetSource的實現(xiàn)在必要時為目標(biāo)創(chuàng)建新的實例來增大池的容量。查看AbstractPoolingTargetSource和你想要使用的具體子類的Javadoc獲取更多關(guān)于它屬性的信息:maxSize是最基礎(chǔ)的,而且永遠(yuǎn)都要求被提供。
在這個例子里,“myInterceptor”是一個攔截器的名字,這個攔截器需要在同一個IoC上下文中被定義。然而,定義對攔截器進(jìn)行池化是不必要的。如果你想要的只是池化而沒有其它通知,就不要設(shè)置interceptorNames屬性。
可以配置Spring來把任何被池化對象轉(zhuǎn)型到org.springframework.aop.target.PoolingConfig接口,這通過一個introduction暴露配置以及當(dāng)前池的大小。你需要像這樣定義一個advisor:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="poolTargetSource"/> <property name="targetMethod" value="getPoolingConfigMixin"/> </bean>
這個advisor可以通過調(diào)用AbstractPoolingTargetSource類上的一個方便的方法來獲得,因此這里使用MethodInvokingFactoryBean。這個advisor名(這里是“poolConfigAdvisor”)必須在提供被池化對象的ProxyFactoryBean里的攔截器名列表里中。
轉(zhuǎn)型看起來像這樣:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize());
![]() |
Note |
---|---|
池化無狀態(tài)服務(wù)對象通常是不必要的。我們不認(rèn)為這(池化)應(yīng)當(dāng)是缺省的選擇,因為多數(shù)無狀態(tài)對象是先天線程安全的,如果資源被緩存,那么對實例進(jìn)行池化會引起很多問題。 |
使用自動代理時池化更加簡單。可以為任何自動代理創(chuàng)建器設(shè)置所使用的TargetSource
建立一個“原型”目標(biāo)源和池化TargetSource很相似。在這個例子里,當(dāng)每次方法調(diào)用時,將創(chuàng)建一個目標(biāo)的新實例。雖然在新版本的JVM中創(chuàng)建一個新對象的代價并不高,但是把新對象織入(滿足它的IoC依賴)可能是很昂貴的。因此如果沒有很好的理由,你不應(yīng)該使用這個方法。
為了做到這點,你可以把上面的poolTargetSource定義修改成下面的形式。(為了清楚說明,修改了bean的名字。)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean>
這里只有一個屬性:目標(biāo)bean的名字。TargetSource的實現(xiàn)使用繼承來確保命名的一致性。就像池化目標(biāo)源那樣,目標(biāo)bean必須是一個原型的bean定義。
如果你需要為每個進(jìn)來的請求(即每個線程)創(chuàng)建一個對象,ThreadLocal目標(biāo)源是很有用的。 ThreadLocal的概念提供了一個JDK范圍的功能,這可以為一個線程透明的保存資源。建立一個 ThreadLocalTargetSource的過程和其它目標(biāo)源幾乎完全一樣:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> </bean>
![]() |
Note |
---|---|
如果不正確的在一個多線程和多類加載器的環(huán)境里使用ThreadLocal,將帶來嚴(yán)重的問題(可能潛在地導(dǎo)致內(nèi)存泄漏)。永遠(yuǎn)記住應(yīng)該把一個threadlocal包裝在其它的類里,并永遠(yuǎn)不要直接使用ThreadLocal本身(當(dāng)然是除了threadlocal包裝類之外)。 同時,永遠(yuǎn)記住正確的設(shè)置(set)和取消(unset)(后者僅僅需要調(diào)用ThreadLocal.set(null))綁定到線程的本地資源。取消在任何情況下都應(yīng)該進(jìn)行,否則也許會導(dǎo)致錯誤的行為。Spring的ThreadLocal支持將為你處理這個問題,所以如果沒有其它正確的處理代碼,永遠(yuǎn)應(yīng)該考慮使用這個功能。 |
Spring AOP被設(shè)計為可擴展的。通過在內(nèi)部使用攔截實現(xiàn)策略,你可以支持已有的環(huán)繞通知,前置通知,異常通知和后置通知之外的任意通知類型,它是開箱即用的。
org.springframework.aop.framework.adapter包是一個SPI包,它允許添加新的自定義通知類型而無需修改核心框架。對于自定義Advice類型的唯一的限制是它必須實現(xiàn)org.aopalliance.aop.Advice這個標(biāo)記接口。
請參考org.springframework.aop.framework.adapter包的Javadoc來了解進(jìn)一步的信息。
posted on 2007-09-07 09:19 劉錚 閱讀(693) 評論(0) 編輯 收藏 所屬分類: Spring