Spring AOP APIs
前一章介紹了Spring 2.0中提供的由@AspectJ和基于Schema的兩種切面定義的AOP。在這個章節里,我們將 討論更底層的Spring AOP API,以及如何在Spring 1.2應用中使用這些API。對于新的應用程序,我們推薦 使用前一章介紹的Spring 2.0 AOP支持,但是當你使用已有系統時,或是閱讀書籍和文章時,很有可能會遇到 Spring 1.2風格的例子。Spring 2.0是完全向前兼容Spring 1.2的,這一章中涉及的所有內容在Spring 2.0里面得到了完全的支持。
讓我們看看Spring是如何處理切入點這個重要概念的。
Spring的切入點模型使得切入點可以獨立于通知類型進行重用,這就使得針對不同 advice使用相同的pointcut成為可能。
org.springframework.aop.Pointcut 是最核心的接口,用來將 通知應用于特定的類和方法,完整的接口定義如下:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
將Pointcut接口分割成有利于重用類和方法匹配的兩部分,以及進行更細粒度的操作組合(例如與另一個方法匹配實現進行“或操作”)。
ClassFilter接口用來將切入點限定在一個給定的類集合中。如果matches()方法總是返回true,所有目標類都將被匹配:
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)方法用來測試這個切入點是否匹配目標類的指定方法。這將在AOP代理被創建的時候執行,這樣可以避免在每次方法調用的時候都執行。如果matches(Method, Class )對于一個給定的方法返回true,并且isRuntime() 也返回true,那么matches(Method, Class , Object[])將在每個方法調用的時候被調用。這使得切入點在通知將被執行前可以查看傳入到方法的參數。
大多數MethodMatcher是靜態的,這意味著isRuntime()方法返回 false。在這種情況下,matches(Method, Class , Object[])永遠不會被調用。
![]() |
Tip |
---|---|
應盡可能地使切入點是靜態的,這就允許AOP框架在AOP代理被創建時緩存對切入點的計算結果。 |
Spring在切入點上支持以下運算: 或和與。
-
或運算表示只需有一個切入點被匹配就執行方法。
-
與運算表示所有的切入點都匹配的情況下才執行。
-
通常或運算要更有用一些。
-
切入點可以使用org.springframework.aop.support.Pointcuts類中的靜態方法來編寫,或者使用同一個包內的ComposablePointcut類。不過使用AspectJ切入點表達式通常會更簡單一些。
從2.0開始,Spring中使用的最重要的切入點類型是org.springframework.aop.aspectj.AspectJExpressionPointcut。 這個切入點使用AspectJ提供的庫來解析滿足AspectJ語法切入點表達式字符串。
可以查看前一章關于所支持的AspectJ切入點原語的討論。
Spring提供了一些很方便的切入點實現。一些是開箱即用的,其它的是切入點應用規范的子集。
靜態切入點基于方法和目標類進行切入點判斷而不考慮方法的參數。在多數情況下,靜態切入點是高效的、最好的選擇。 Spring只在第一次調用方法時執行靜態切入點:以后每次調用這個方法時就不需要再執行。
讓我們考慮Spring中的一些靜態切入點實現。
顯而易見,一種描述靜態切入點的方式是使用正則表達式。包含Spring在內的一些AOP框架都支持這種方式。 org.springframework.aop.support.Perl5RegexpMethodPointcut是一個最基本的正則表達式切入點, 它使用Perl 5正則表達式語法。Perl5RegexpMethodPointcut依賴Jakarta ORO進行正則表達式匹配。 Spring也提供了JdkRegexpMethodPointcut類,它使用JDK 1.4或更高版本里提供的正則表達式支持。
使用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。 可以通過設置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可以和任何通知類型一起使用
動態切入點比起靜態切入點在執行時要消耗更多的資源。它們同時計算方法參數和靜態信息。 這意味著它們必須在每次方法調用時都被執行;由于參數的不同,評估結果不能被緩存。
動態切入點的常見例子是控制流切入點。
Spring控制流切入點在概念上和AspectJ的cflow 切入點很相似, 雖然它的功能不如后者那么強大。(目前還不能讓一個切入點在另外一個切入點所評估的連接點處執行)。 一個控制流切入點匹配當前的調用棧。例如,一個連接點被com.mycompany.web包內的一個 方法或者SomeCaller類調用,切入點就可能被激活。 控制流切入點是由org.springframework.aop.support.ControlFlowPointcut 類聲明的。
![]() |
Note |
---|---|
在執行時控制流切入點的開銷是非常昂貴的,甚至與其它動態切入點比起來也是如此。在Java 1.4里,它的開銷差不多是其它動態切入點的5倍;在Java 1.3中,這個比例甚至達到10倍。 |
Spring提供了很多有用的切入點基礎類來幫助你實現你自己的切入點。
因為靜態切入點是最常用的,你可能會像下面那樣繼承StaticMethodMatcherPointcut。這只需要實現一個抽象方法 (雖然也可以覆蓋其它方法來定制行為):
class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } }
動態切入點也有很多基類。
你可以用Spring 1.0 RC2和更高版本里的自定義切入點及通知類型。
現在讓我們看一下SPring AOP是怎樣處理通知的。
每個通知都是一個Spring bean。一個通知實例既可以被所有被通知的對象共享,也可以被每個被通知對象獨占。 這根據設置類共享(per-class)或基于實例(per-instance)的參數來決定。
類共享通知經常會被用到。它很適合用作通用的通知例如事務advisor。這些advisor不依賴于代理對象的狀態也不會向代理對象添加新的狀態;它們僅僅在方法和參數上起作用。
基于實例的通知很適合用作導入器來支持混合類型。在這種情況下,通知向代理對象添加狀態。
在同一個AOP代理里混合使用類共享和基于實例的通知是可能的。
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 |
---|---|
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 |
---|---|
前置通知可以和任何切入點一起使用。 |
如果連接點拋出異常,異常通知(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 |
---|---|
異常通知可以和任何切入點一起使用。 |
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 |
---|---|
后置通知可以和任何切入點一起使用。 |
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和有狀態混合類型。
在Spring里,一個advisor是一個僅僅包含一個通知對象和與之關聯的切入點表達式的切面。
除了引入這種特殊形式,任何advisor都可以和任何通知一起工作。 org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor類。例如,它可以和: MethodInterceptor,BeforeAdvice 或者 ThrowsAdvice一起使用。
在Spring里有可能在同一個AOP代理里模糊advisor和通知類型。例如,你可以在一個代理配置里使用一個interception環繞通知,一個異常通知和一個前置通知:Spring將負責自動創建所需的攔截器鏈。
如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來管理你的業務對象--這正是你應該做的--你也許會想要使用Spring中關于AOP的FactoryBean。(記住使用工廠bean引入一個間接層之后,我們就可以創建不同類型的對象了)。
![]() |
Note |
---|---|
Spring 2.0的AOP支持也在底層使用工廠bean。 |
在Spring里創建一個AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個類對應用的切入點和通知提供了完整的控制能力(包括它們的應用順序)。然而如果你不需要這種控制,你會喜歡更簡單的方式。
像其它的FactoryBean實現一樣,ProxyFactoryBean引入了一個間接層。如果你定義一個名為foo的ProxyFactoryBean, 引用foo的對象看到的將不是ProxyFactoryBean實例本身,而是一個ProxyFactoryBean實現里getObject() 方法所創建的對象。 這個方法將創建一個AOP代理,它包裝了一個目標對象。
使用ProxyFactoryBean或者其它IoC相關類帶來的最重要的好處之一就是創建AOP代理,這意味著通知和切入點也可以由IoC來管理。這是一個強大的功能并使得某些特定的解決方案成為可能, 而這些用其它AOP框架很難做到。例如,一個通知也許本身也要引用應用程序對象(不僅僅是其它AOP框架中也可以訪問的目標對象),這令你可以從依賴注射的可拔插特性中獲益。
通常情況下Spring提供了大多數的FactoryBean實現,ProxyFactoryBean類本身也是一個JavaBean。它的屬性被用來:
-
指定你希望代理的目標對象
-
指定是否使用CGLIB(查看下面叫做Section 7.5.3, “基于JDK和CGLIB的代理”的小節)。
一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(這個類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:
-
proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口。如果這個屬性值被設為true,CGLIB代理將被創建(可以參看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節)。
-
optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全了解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用于CGLIB代理;對于JDK動態代理(缺省代理)無效。
-
frozen:用來控制代理工廠被配置之后,是否還允許修改通知。缺省值為false(即在代理被配置之后,不允許修改代理的配置)。
-
exposeProxy:決定當前代理是否被保存在一個ThreadLocal中以便被目標對象訪問。(目標對象本身可以通過MethodInvocation來訪問,因此不需要ThreadLocal。) 如果個目標對象需要獲取代理而且exposeProxy屬性被設為true,目標對象可以使用AopContext.currentProxy()方法。
-
aopProxyFactory:使用AopProxyFactory的實現。這提供了一種方法來自定義是否使用動態代理,CGLIB或其它代理策略。 缺省實現將根據情況選擇動態代理或者CGLIB。一般情況下應該沒有使用這個屬性的需要;它是被設計來在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean中需要說明的其它屬性包括:
-
proxyInterfaces:需要代理的接口名的字符串數組。如果沒有提供,將為目標類使用一個CGLIB代理(也可以查看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節)。
-
interceptorNames:Advisor的字符串數組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優先服務。就是說列表里的第一個攔截器將能夠第一個攔截調用。
這里的名字是當前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因為這會導致ProxyFactoryBean忽略通知的單例設置。
你可以把一個攔截器的名字加上一個星號作為后綴(*)。這將導致這個應用程序里所有名字以星號之前部分開頭的advisor都被應用。你可以在Section 7.5.6, “使用“全局”advisor” 發現一個使用這個特性的例子。
-
單例:工廠是否應該返回同一個對象,不論方法getObject()被調用的多頻繁。多個FactoryBean實現都提供了這個方法。缺省值是true。如果你希望使用有狀態的通知--例如,有狀態的mixin--可以把單例屬性的值設置為false來使用原型通知。
這個小節作為說明性文檔,解釋了對于一個目標對象(需要被代理的對象),ProxyFactryBean是如何決定究竟創建一個基于JDK還是CGLIB的代理的。
![]() |
Note |
---|---|
ProxyFactoryBean需要創建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。現在ProxyFactoryBean在關于自動檢測接口方面使用了與TransactionProxyFactoryBean相似的語義。 |
如果一個需要被代理的目標對象的類(后面將簡單地稱它為目標類)沒有實現任何接口,那么一個基于CGLIB的代理將被創建。這是最簡單的場景,因為JDK代理是基于接口的,沒有接口意味著沒有使用JDK進行代理的可能。 在目標bean里將被插入探測代碼,通過interceptorNames屬性給出了攔截器的列表。注意一個基于CGLIB的代理將被創建即使ProxyFactoryBean的proxyTargetClass屬性被設置為false。 (很明顯這種情況下對這個屬性進行設置是沒有意義的,最好把它從bean的定義中移除,因為雖然這只是個多余的屬性,但在許多情況下會引起混淆。)
如果目標類實現了一個(或者更多)接口,那么創建代理的類型將根據ProxyFactoryBean的配置來決定。
如果ProxyFactoryBean的proxyTargetClass屬性被設為true,那么一個基于CGLIB的代理將創建。這樣的規定是有意義的,遵循了最小驚訝法則(保證了設定的一致性)。 甚至當ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,而proxyTargetClass屬性被設置為true仍然將實際使用基于CGLIB的代理。
如果ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,一個基于JDK的代理將被創建。被創建的代理將實現所有在proxyInterfaces屬性里被說明的接口;如果目標類實現了全部在proxyInterfaces屬性里說明的接口以及一些額外接口,返回的代理將只實現說明的接口而不會實現那些額外接口。
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設置,但是目標類實現了一個(或者更多)接口,那么ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口, 一個基于JDK的代理將被創建。被實際代理的接口將是目標類所實現的全部接口;實際上,這和在proxyInterfaces屬性中列出目標類實現的每個接口的情況是一樣的。然而,這將顯著地減少工作量以及輸入錯誤的可能性。
讓我們看一個關于ProxyFactoryBean的簡單例子。這個例子涉及:
-
一個將被代理的目標bean。在下面的例子里這個bean是“personTarget”。
-
被用來提供通知的一個advisor和一個攔截器。
-
一個AOP代理bean的定義,它說明了目標對象(personTarget bean)以及需要代理的接口,還包括需要被應用的通知。
<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屬性接受一組字符串:當前工廠中攔截器或advisorbean的名字。攔截器,advisor,前置, 后置和異常通知對象都可以在這里被使用。這里advisor的順序是很重要的。
![]() |
Note |
---|---|
你也許很奇怪為什么這個列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設置為false,它必須返回獨立的代理實例。如果任何advisor本身是一個原型,則每次都返回一個獨立實例,因此它必須能夠從工廠里獲得原型的一個實例;保存一個引用是不夠的。 |
上面“person” bean的定義可以被用來取代一個Person接口的實現,就像下面這樣:
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的屬性。就像我們關心的那樣,AOP代理可以透明地取代一個“真實”的person接口實現。然而,它的類將是一個動態代理類。 它可以被轉型成Advised接口(將在下面討論)。
就像下面這樣,你可以使用一個匿名內部bean來隱藏目標和代理之間的區別。僅僅ProxyFactoryBean的定義有所不同;通知的定義只是由于完整性的原因而被包括進來:
<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類型對象的情況,這是有好處的:如果你希望阻止應用程序上下文的用戶獲取一個指向未通知對象的引用或者希望避免使用Spring IoC 自動織入 時的混淆。 按理說ProxyFactoryBean定義還有一個優點是它是自包含的。然而,有時能夠從工廠里獲取未通知的目標也是一個優點:例如,在某些測試場景里。
如果你需要代理一個類而不是代理一個或是更多接口,那么情況將是怎樣?
想象在我們上面的例子里,不存在Person接口:我們需要通知一個叫做Person的類,它沒有實現任何業務接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動態代理。 這只需簡單地把上面ProxyFactoryBean的proxyTargetClass屬性設為true。雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時,通知沒有實現接口的類的能力是非常有用的。(通常情況下,Spring沒有任何規定。它只是讓你很容易根據實際情況選擇最好的解決方案,避免強迫使用特定方式)。
也許你希望你能夠在任何情況下都強制使用CGLIB,甚至在你使用接口的時候也這樣做。
CGLIB通過在運行時生成一個目標類的子類來進行代理工作。Spring配置這個生成的子類對原始目標對象的方法調用進行托管:子類實現了裝飾器(Decorator)模式,把通知織入。
CGLIB的代理活動應當對用戶是透明的。然而,有一些問題需要被考慮:
-
Final方法不可以被通知,因為它們不能被覆蓋。
-
你需要在你的類路徑里有CGLIB 2的庫;使用動態代理的話只需要JDK。
在CGLIB代理和動態代理之間的速度差別是很小的。在Spring 1.0中,動態代理會快一點點。但這點可能在將來被改變。這種情況下,選擇使用何種代理時速度不應該成為決定性的理由。
通過在一個攔截器名后添加一個星號,所有bean名字與星號之前部分相匹配的通知都將被加入到advisor鏈中。這讓你很容易添加一組標準的“全局”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"/>
你也許需要許多相似的代理定義,特別是定義事務性代理的時候。使用父子bean定義,以及內部bean定義,可以讓代理定義大大得到極大的簡化。
首先從父bean開始,為代理bean創建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本身將永遠不會被初始化,所以實際上是不完整的。而后每個需要創建的代理都是這個bean定義的子bean定義,它們把代理的目標類包裝為一個內部bean定義,因為目標對象本身將不會被單獨使用。
<bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean>
當然你可以覆蓋從模版中繼承的屬性,例如在下面這個例子里的事務傳播設置:
<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>
要注意上面例子中我們已經明確地通過設定abstract屬性把父bean定義標注為abstract,在前面的章節里有描述,這樣它實際上不能被初始化。 缺省情況下應用程序上下文(不僅僅是bean工廠)將預先初始化所有的實例為單例。因此下面這點是很重要的(至少對于單例bean來說),如果你有一個(父)bean定義你希望僅僅作為模版使用,而這個定義說明了一個類,你必須把abstract參數設置為true,否則應用程序上下文將試圖預先初始化它。
使用Spring通過編程創建AOP代理是很容易的。這使你可以使用Spring AOP而不必依賴于Spring IoC。
下面的清單顯示了如何使用一個攔截器和一個advisor來為一個目標對象來創建一個代理。目標對象實現的接口將被自動代理:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addInterceptor(myMethodInterc ptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
第一步是創建一個類型為org.springframework.aop.framework.ProxyFactory的對象。你可以像上面例子里那樣使用一個目標對象來創建它,或者在一個可選的構造器里說明需要被代理的接口。
你可以添加攔截器或advisor,并在ProxyFactory的生命周期里操作它們。如果你加入一個IntroductionInterceptionAroundAdvisor,你可以讓代理實現額外的接口。
在ProxyFactory里也有很方便的方法(繼承自AdvisedSupport)允許你加入其它的通知類型例如前置和異常通知。AdvisedSupport是ProxyFactory 和ProxyFactoryBean的共同父類。
![]() |
Tip |
---|---|
在大多數應用程序里,把AOP代理的創建和IoC框架集成是最佳實踐。通常情況下我們推薦你在Java代碼外進行AOP的配置。 |
在創建了AOP代理之后,你能夠使用org.springframework.aop.framework.Advised接口對它們進行管理。任何AOP代理都能夠被轉型為這個接口,不論它實現了哪些其它接口。這個接口包括下面的方法:
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()方法將為每個已經被加入工廠的advisor,攔截器或者其它通知類型返回一個advisor。如果你曾經添加一個advisor,那么所返回的advisor將是你加入的對象。 如果你曾經加入一個攔截器或者其它通知類型,Spring將把它們包裝在一個advisor里,后者使用一個永遠返回true的切入點。因此如果你曾經加入一個MethodInterceptor, 返回的advisor將是一個DefaultPointcutAdvisor,它可以返回你加入的MethodInterceptor和一個匹配所有類和方法的切入點。
addAdvisor()方法可以用來添加任何advisor。通常保存切入點和通知的advisor是DefaultPointcutAdvisor,它可以用于任何通知或切入點(但不包括引入類型)。
缺省情況下,你可以加入或移除advisor或者攔截器甚至當代理已經被創建之后。唯一的限制是無法加入或者移除一個引入advisor,因為工廠中獲得的已有代理不能顯示接口的改變(你可以通過從工廠里獲取一個新的代理來避免這個問題)。
下面是一個簡單的例子,它把一個AOP代理轉型為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 |
---|---|
在一個實際運行的系統里,修改一個業務對象上的通知是否明智是個問題,雖然無疑在某些情況下這樣做是合理的。然而這在開發中是很有用的: 例如,在測試的時候。對于希望測試的方法調用,有時我發現把測試代碼加入到一個攔截器或者其它通知里是非常有用的。(例如,通知可以與目標方法存在于同一個事務里,在把事務標記為回滾之前可以用SQL來檢查數據庫是否被正確的更新了。) |
依賴于你怎樣創建代理,你通常可以設置一個 frozen標志,在這種情況下 Advised的isFrozen()方法將返回true,任何增加或者移除通知的修改都會導致一個AopConfigException異常。 在某些情況下這種凍結被通知對象狀態的能力是很有用的:例如,防止調用代碼來移除一個進行安全檢查的攔截器。在Spring 1.1中它也被用來允許激進優化,如果已經知道不需要運行時對通知進行修改的話。
到目前為止我們已經考慮了如何使用ProxyFactoryBean或者類似的工廠bean來顯式創建AOP代理。
Spring也允許我們使用“自動代理”的bean定義,可以自動對被選中的bean定義進行代理。這建立在Spring的“bean post processor”功能上,后者允許在容器加載時修改任何bean的定義。
在這個模型下,你在你的XML bean定義文件中建立一些特定的bean定義來配置自動代理功能。這允許你僅僅聲明那些將被自動代理的適當目標:你不需要使用ProxyFactoryBean。
有兩種方式可以做到這點:
-
使用一個引用當前上下文中特定bean的自動代理創建器。
-
一個專用自動代理的創建需要被單獨考慮;自動代理創建由源代碼級別的元數據屬性驅動。
org.springframework.aop.framework.autoproxy包提供了下列標準自動代理創建器。
BeanNameAutoProxyCreator為名字匹配字符串或者通配符的bean自動創建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的主要目的是把相同的配置一致地應用到多個對象,并且使用最少量的配置。一個流行的選擇是把聲明式事務應用到多個對象上。
那些名字匹配的Bean定義,例如上面的例子里的“jdkMyBean”和“onlyJdk”,本身只是目標類的普通bean定義。一個AOP對象將被BeanNameAutoProxyCreator自動創建。 相同的通知將被應用到全部匹配的bean上。注意如果advisor被使用(而不是像上面例子里那樣使用攔截器),對于不同bean可以應用不同的切入點。
一個更加通用而且強大得多的自動代理創建器是DefaultAdvisorAutoProxyCreator。它自動應用當前上下文中適當的advisor,無需在自動代理advisor的bean定義中包括bean的名字。 比起BeanNameAutoProxyCreator,它提供了同樣關于一致性配置的優點而避免了前者的重復性。
使用這個功能將涉及:
-
說明一個 DefaultAdvisorAutoProxyCreator的bean定義
-
在同一個或者相關的上下文中說明任意數量的advisor。注意這些必須是advisor而不僅僅是攔截器或者其它通知。這點是必要的因為必須有一個切入點被評估,以便檢查每個通知候選bean定義的合適性。
DefaultAdvisorAutoProxyCreator將自動評估包括在每個advisor中的切入點,來看看它應當應用哪個(如果有的話)通知到每個業務對象(例如例子里的“businessObject1”和“businessObject2”)。
這意味著可以向每個業務對象應用任意數量的advisor。對于一個業務對象,如果沒有任何advisor中的切入點匹配它的任何方法,這個對象將不會被代理。當為新的業務對象加入bean定義時,如果有必要它們將自動被代理。
通常自動代理的好處是它讓調用者或者被依賴對象不能得到一個沒有通知過的對象。在這個ApplicationContext上調用getBean("businessObject1")將返回一個AOP代理,而不是目標業務對象。(前面顯示的“內部bean”也提供了同樣的優點。)
<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"/>
如果你想要把相同的通知一致性地應用到許多業務對象上,DefaultAdvisorAutoProxyCreator是非常有用的。一旦框架的定義已經完成,你可以簡單地加入新的業務對象而不必包括特定的代理配置。你也可以很容易的去掉額外的切面--例如,跟蹤或者性能監視切面--僅僅對配置作很小的修改。
DefaultAdvisorAutoProxyCreator支持過濾(通過使用一個命名約定讓只有特定的advisor被評估,允許在同一個工廠里使用多個不同配置的AdvisorAutoProxyCreator)和排序。advisor可以實現org.springframework.core.Ordered接口來確保以正確的順序被應用。 上面例子里的TransactionAttributeSourceAdvisor 有一個可配置的序號值;缺省情況下是沒有排序的。
一個非常重要的自動代理類型是由元數據驅動的。這提供了一種和.NET ServicedComponents相似的編程模型。作為使用類似EJB里的XML描述符的替代,對于事務管理和其它企業服務的配置都將被保存在源代碼級別的屬性里。
在這個情況下,你使用DefaultAdvisorAutoProxyCreator和可以理解元數據屬性的advisor。元數據被保存在候選advisor里的切入點部分,而不是在自動代理創建類本身。
這是一個DefaultAdvisorAutoProxyCreator的特殊例子,它本身沒有什么特別。(元數據的相關代碼保存在advisor內的切入點里,而不是AOP框架本身)。
JPetStore示例應用程序的/attributes 目錄顯示了如何使用參數驅動的自動代理。在這個例子里,不需要使用TransactionProxyFactoryBean。因為使用了元數據相關的切入點, 所以簡單在業務對象上定義事務屬性就足夠了。在/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定義(名字是不重要的,因此甚至可以在定義里省略它)將在當前應用程序上下文中查找所有合適的切入點。在這個例子里,TransactionAttributeSourceAdvisor類型的“transactionAdvisor”bean定義將應用到帶有一個事務屬性的類或方法上。 TransactionAttributeSourceAdvisor的構造器依賴于一個TransactionInterceptor。這個例子里通過自動織入解決了這個問題。AttributesTransactionAttributeSource依賴于一個org.springframework.metadata.Attributes接口的實現。 在這個代碼片斷里,“attributes”bean使用Jakarta Commons Attributes API來獲取屬性信息以滿足這個要求。(應用程序代碼必須已經使用Commons Attribut來es的編譯任務編譯過了。)
JPetStore示例應用程序的 /annotation 目錄包括了一個由JDK 1.5+注解驅動的自動代理的模擬例子。下面的配置允許自動檢測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定義,后者沒有被包括在這個通用的文件里(雖然它可以被包括在這里)因為它在應用程序的事務需求規范中指定(在這個例子里使用JTA,而在其它情況下,可以是Hibernate,JDO或者JDBC):
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
![]() |
Tip |
---|---|
如果你只需要聲明式事務管理,使用這些通用的XML定義將導致Spring自動代理所有帶有事務屬性的類或者方法。你將不需要直接使用AOP工作, 這個編程模型和.NET的ServicedComponents相似。 |
這個架構是可以擴展的。可以在自定義屬性的基礎上進行自動代理。你所需要做的是:
-
定義你自己的自定義屬性
-
使用必要的通知說明一個advisor,也包括一個切入點,后者可以被類或者方法上的自定義屬性觸發。你也許能夠使用已有的通知,而僅僅實現一個能夠處理自定義屬性的靜態切入點。
可以讓這些advisor對于每個被通知對象(例如,mixins)都是唯一的:僅僅需要在bean定義中被定義為原型而不是單例。例如,在上面所顯示的Spring測試集中的LockMixin引入攔截器可以和一個屬性驅動的切入點聯合定位一個mixin, 像這里顯示的這樣。我們使用通用的DefaultPointcutAdvisor,使用JavaBean屬性進行配置:
<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" ...
如果參數相關的切入點匹配anyBean或其它bean定義里的任何方法,mixin將被應用。注意lockMixin和lockableAdvisor的定義都是原型。myAttributeAwarePointcut切入點可以是個單例,因為它沒有為單個被通知對象保持狀態。
Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource接口進行描述。這個接口負責返回一個實現連接點的“目標對象(target object)”。每當AOP代理處理一個方法調用時都會向TargetSource的實現請求一個目標實例。
使用Spring AOP的開發者通常不需要直接和TargetSource打交道,但這提供了一種強大的方式來支持池化(pooling),熱交換(hot swappable)和其它高級目標。例如,一個使用池來管理實例的TargetSource可以為每個調用返回一個不同的目標實例。
如果你不指定一個TargetSource,一個缺省實現將被使用,它包裝一個本地對象。對于每次調用它將返回相同的目標(像你期望的那樣)。
讓我們看看Spring提供的標準目標源(target source)以及如何使用它們。
![]() |
Tip |
---|---|
當使用一個自定義的目標源,你的目標通常需要是一個原型而不是一個單例的bean定義。這允許Spring在必要時創建新的目標實例。 |
org.springframework.aop.target.HotSwappableTargetSource允許當調用者保持引用的時候,切換一個AOP代理的目標。
修改目標源的目標將立即生效。 HotSwappableTargetSource是線程安全的。
你可以通過HotSwappableTargetSource的 swap()方法來改變目標,就像下面那樣:
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()調用修改了swappable bean的目標。保持對這個bean的引用的客戶將不知道發生了這個修改,但是將可以立即點擊新的目標。
這個例子沒有添加任何通知--也不必為使用一個TargetSource添加任何通知--當然任何TargetSource都可以與任意通知聯合使用。
使用一個池化目標源提供了和無狀態session EJB類似的編程模型,它維護一個包括相同實例的池,方法調用結束后將把對象釋放回池中。
Spring池化和SLSB池化之間的一個決定性區別是Spring池化功能可以用于任何POJO。就像Spring通常情況下那樣,這個服務是非侵入式的。
Spring對Jakarta Commons Pool 1.3提供了開箱即用的支持,后者提供了一個相當有效的池化實現。要使用這個特性,你需要在應用程序路徑中存在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>
注意目標對象--例子里的“businessObjectTarget”--必須是個原型。這允許PoolingTargetSource的實現在必要時為目標創建新的實例來增大池的容量。查看AbstractPoolingTargetSource和你想要使用的具體子類的Javadoc獲取更多關于它屬性的信息:maxSize是最基礎的,而且永遠都要求被提供。
在這個例子里,“myInterceptor”是一個攔截器的名字,這個攔截器需要在同一個IoC上下文中被定義。然而,定義對攔截器進行池化是不必要的。如果你想要的只是池化而沒有其它通知,就不要設置interceptorNames屬性。
可以配置Spring來把任何被池化對象轉型到org.springframework.aop.target.PoolingConfig接口,這通過一個introduction暴露配置以及當前池的大小。你需要像這樣定義一個advisor:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="poolTargetSource"/> <property name="targetMethod" value="getPoolingConfigMixin"/> </bean>
這個advisor可以通過調用AbstractPoolingTargetSource類上的一個方便的方法來獲得,因此這里使用MethodInvokingFactoryBean。這個advisor名(這里是“poolConfigAdvisor”)必須在提供被池化對象的ProxyFactoryBean里的攔截器名列表里中。
轉型看起來像這樣:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize());
![]() |
Note |
---|---|
池化無狀態服務對象通常是不必要的。我們不認為這(池化)應當是缺省的選擇,因為多數無狀態對象是先天線程安全的,如果資源被緩存,那么對實例進行池化會引起很多問題。 |
使用自動代理時池化更加簡單。可以為任何自動代理創建器設置所使用的TargetSource
建立一個“原型”目標源和池化TargetSource很相似。在這個例子里,當每次方法調用時,將創建一個目標的新實例。雖然在新版本的JVM中創建一個新對象的代價并不高,但是把新對象織入(滿足它的IoC依賴)可能是很昂貴的。因此如果沒有很好的理由,你不應該使用這個方法。
為了做到這點,你可以把上面的poolTargetSource定義修改成下面的形式。(為了清楚說明,修改了bean的名字。)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean>
這里只有一個屬性:目標bean的名字。TargetSource的實現使用繼承來確保命名的一致性。就像池化目標源那樣,目標bean必須是一個原型的bean定義。
如果你需要為每個進來的請求(即每個線程)創建一個對象,ThreadLocal目標源是很有用的。 ThreadLocal的概念提供了一個JDK范圍的功能,這可以為一個線程透明的保存資源。建立一個 ThreadLocalTargetSource的過程和其它目標源幾乎完全一樣:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> </bean>
![]() |
Note |
---|---|
如果不正確的在一個多線程和多類加載器的環境里使用ThreadLocal,將帶來嚴重的問題(可能潛在地導致內存泄漏)。永遠記住應該把一個threadlocal包裝在其它的類里,并永遠不要直接使用ThreadLocal本身(當然是除了threadlocal包裝類之外)。 同時,永遠記住正確的設置(set)和取消(unset)(后者僅僅需要調用ThreadLocal.set(null))綁定到線程的本地資源。取消在任何情況下都應該進行,否則也許會導致錯誤的行為。Spring的ThreadLocal支持將為你處理這個問題,所以如果沒有其它正確的處理代碼,永遠應該考慮使用這個功能。 |
Spring AOP被設計為可擴展的。通過在內部使用攔截實現策略,你可以支持已有的環繞通知,前置通知,異常通知和后置通知之外的任意通知類型,它是開箱即用的。
org.springframework.aop.framework.adapter包是一個SPI包,它允許添加新的自定義通知類型而無需修改核心框架。對于自定義Advice類型的唯一的限制是它必須實現org.aopalliance.aop.Advice這個標記接口。
請參考org.springframework.aop.framework.adapter包的Javadoc來了解進一步的信息。
posted on 2007-09-07 09:19 劉錚 閱讀(693) 評論(0) 編輯 收藏 所屬分類: Spring