Spring AOP APIs

          Chapter 7. Spring AOP APIs

          7.1. 簡(jiǎn)介

          前一章介紹了Spring 2.0中提供的由@AspectJ和基于Schema的兩種切面定義的AOP。在這個(gè)章節(jié)里,我們將 討論更底層的Spring AOP API,以及如何在Spring 1.2應(yīng)用中使用這些API。對(duì)于新的應(yīng)用程序,我們推薦 使用前一章介紹的Spring 2.0 AOP支持,但是當(dāng)你使用已有系統(tǒng)時(shí),或是閱讀書籍和文章時(shí),很有可能會(huì)遇到 Spring 1.2風(fēng)格的例子。Spring 2.0是完全向前兼容Spring 1.2的,這一章中涉及的所有內(nèi)容在Spring 2.0里面得到了完全的支持。

          7.2. Spring中的切入點(diǎn)API

          讓我們看看Spring是如何處理切入點(diǎn)這個(gè)重要概念的。

          7.2.1. 概念

          Spring的切入點(diǎn)模型使得切入點(diǎn)可以獨(dú)立于通知類型進(jìn)行重用,這就使得針對(duì)不同 advice使用相同的pointcut成為可能。

          org.springframework.aop.Pointcut 是最核心的接口,用來(lái)將 通知應(yīng)用于特定的類和方法,完整的接口定義如下:

          public interface Pointcut {
          ClassFilter getClassFilter();
          MethodMatcher getMethodMatcher();
          }

          Pointcut接口分割成有利于重用類和方法匹配的兩部分,以及進(jìn)行更細(xì)粒度的操作組合(例如與另一個(gè)方法匹配實(shí)現(xiàn)進(jìn)行“或操作”)。

          ClassFilter接口用來(lái)將切入點(diǎn)限定在一個(gè)給定的類集合中。如果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)方法用來(lái)測(cè)試這個(gè)切入點(diǎn)是否匹配目標(biāo)類的指定方法。這將在AOP代理被創(chuàng)建的時(shí)候執(zhí)行,這樣可以避免在每次方法調(diào)用的時(shí)候都執(zhí)行。如果matches(Method, Class )對(duì)于一個(gè)給定的方法返回true,并且isRuntime() 也返回true,那么matches(Method, Class , Object[])將在每個(gè)方法調(diào)用的時(shí)候被調(diào)用。這使得切入點(diǎn)在通知將被執(zhí)行前可以查看傳入到方法的參數(shù)。

          大多數(shù)MethodMatcher是靜態(tài)的,這意味著isRuntime()方法返回 false。在這種情況下,matches(Method, Class , Object[])永遠(yuǎn)不會(huì)被調(diào)用。

          [Tip] Tip

          應(yīng)盡可能地使切入點(diǎn)是靜態(tài)的,這就允許AOP框架在AOP代理被創(chuàng)建時(shí)緩存對(duì)切入點(diǎn)的計(jì)算結(jié)果。

          7.2.2. 切入點(diǎn)實(shí)施

          Spring在切入點(diǎn)上支持以下運(yùn)算:

          • 或運(yùn)算表示只需有一個(gè)切入點(diǎn)被匹配就執(zhí)行方法。

          • 與運(yùn)算表示所有的切入點(diǎn)都匹配的情況下才執(zhí)行。

          • 通常或運(yùn)算要更有用一些。

          • 切入點(diǎn)可以使用org.springframework.aop.support.Pointcuts類中的靜態(tài)方法來(lái)編寫,或者使用同一個(gè)包內(nèi)的ComposablePointcut類。不過(guò)使用AspectJ切入點(diǎn)表達(dá)式通常會(huì)更簡(jiǎn)單一些。

          7.2.3. AspectJ切入點(diǎn)表達(dá)式

          從2.0開(kāi)始,Spring中使用的最重要的切入點(diǎn)類型是org.springframework.aop.aspectj.AspectJExpressionPointcut。 這個(gè)切入點(diǎn)使用AspectJ提供的庫(kù)來(lái)解析滿足AspectJ語(yǔ)法切入點(diǎn)表達(dá)式字符串。

          可以查看前一章關(guān)于所支持的AspectJ切入點(diǎn)原語(yǔ)的討論。

          7.2.4. 便利的切入點(diǎn)實(shí)現(xiàn)

          Spring提供了一些很方便的切入點(diǎn)實(shí)現(xiàn)。一些是開(kāi)箱即用的,其它的是切入點(diǎn)應(yīng)用規(guī)范的子集。

          7.2.4.1. 靜態(tài)切入點(diǎn)

          靜態(tài)切入點(diǎn)基于方法和目標(biāo)類進(jìn)行切入點(diǎn)判斷而不考慮方法的參數(shù)。在多數(shù)情況下,靜態(tài)切入點(diǎn)是高效的、最好的選擇。 Spring只在第一次調(diào)用方法時(shí)執(zhí)行靜態(tài)切入點(diǎn):以后每次調(diào)用這個(gè)方法時(shí)就不需要再執(zhí)行。

          讓我們考慮Spring中的一些靜態(tài)切入點(diǎn)實(shí)現(xiàn)。

          7.2.4.1.1. 正則表達(dá)式切入點(diǎn)

          顯而易見(jiàn),一種描述靜態(tài)切入點(diǎn)的方式是使用正則表達(dá)式。包含Spring在內(nèi)的一些AOP框架都支持這種方式。 org.springframework.aop.support.Perl5RegexpMethodPointcut是一個(gè)最基本的正則表達(dá)式切入點(diǎn), 它使用Perl 5正則表達(dá)式語(yǔ)法。Perl5RegexpMethodPointcut依賴Jakarta ORO進(jìn)行正則表達(dá)式匹配。 Spring也提供了JdkRegexpMethodPointcut類,它使用JDK 1.4或更高版本里提供的正則表達(dá)式支持。

          使用Perl5RegexpMethodPointcut類,你可以提供一組模式字符串。 如果其中任意一個(gè)模式匹配,切入點(diǎn)將被解析為true。(實(shí)際上就是這些切入點(diǎn)的或集。)

          下面顯示用法:

          <bean id="settersAndAbsquatulatePointcut"
          class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
          <property name="patterns">
          <list>
          <value>.*set.*</value>
          <value>.*absquatulate</value>
          </list>
          </property>
          </bean>

          Spring提供了一個(gè)方便的類 RegexpMethodPointcutAdvisor, 它也允許我們引用一個(gè)通知(記住這里一個(gè)通知可以是攔截器,前置通知(before advice),異常通知(throws advice)等類型中的一個(gè))。 在背后,如果使用J2SE 1.4或者以上版本,Spring將使用JdkRegexpMethodPointcut,在之前版本的虛擬機(jī)上,Spring將退回去使用Perl5RegexpMethodPointcut。 可以通過(guò)設(shè)置perl5屬性為true來(lái)強(qiáng)制使用Perl5RegexpMethodPointcut。使用RegexpMethodPointcutAdvisor可以簡(jiǎn)化織入,因?yàn)橐粋€(gè)bean可以同時(shí)作為切入點(diǎn)和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可以和任何通知類型一起使用

          7.2.4.1.2. 屬性驅(qū)動(dòng)的切入點(diǎn)

          一個(gè)重要的靜態(tài)切入點(diǎn)是元數(shù)據(jù)驅(qū)動(dòng)(metadata-driven)切入點(diǎn)。這使用元數(shù)據(jù)參數(shù):特別是源代碼級(jí)別的元數(shù)據(jù)。

          7.2.4.2. 動(dòng)態(tài)切入點(diǎn)

          動(dòng)態(tài)切入點(diǎn)比起靜態(tài)切入點(diǎn)在執(zhí)行時(shí)要消耗更多的資源。它們同時(shí)計(jì)算方法參數(shù)和靜態(tài)信息。 這意味著它們必須在每次方法調(diào)用時(shí)都被執(zhí)行;由于參數(shù)的不同,評(píng)估結(jié)果不能被緩存。

          動(dòng)態(tài)切入點(diǎn)的常見(jiàn)例子是控制流切入點(diǎn)。

          7.2.4.2.1. 控制流切入點(diǎn)

          Spring控制流切入點(diǎn)在概念上和AspectJ的cflow 切入點(diǎn)很相似, 雖然它的功能不如后者那么強(qiáng)大。(目前還不能讓一個(gè)切入點(diǎn)在另外一個(gè)切入點(diǎn)所評(píng)估的連接點(diǎn)處執(zhí)行)。 一個(gè)控制流切入點(diǎn)匹配當(dāng)前的調(diào)用棧。例如,一個(gè)連接點(diǎn)被com.mycompany.web包內(nèi)的一個(gè) 方法或者SomeCaller類調(diào)用,切入點(diǎn)就可能被激活。 控制流切入點(diǎn)是由org.springframework.aop.support.ControlFlowPointcut 類聲明的。

          [Note] Note

          在執(zhí)行時(shí)控制流切入點(diǎn)的開(kāi)銷是非常昂貴的,甚至與其它動(dòng)態(tài)切入點(diǎn)比起來(lái)也是如此。在Java 1.4里,它的開(kāi)銷差不多是其它動(dòng)態(tài)切入點(diǎn)的5倍;在Java 1.3中,這個(gè)比例甚至達(dá)到10倍。

          7.2.5. 切入點(diǎn)的基類

          Spring提供了很多有用的切入點(diǎn)基礎(chǔ)類來(lái)幫助你實(shí)現(xiàn)你自己的切入點(diǎn)。

          因?yàn)殪o態(tài)切入點(diǎn)是最常用的,你可能會(huì)像下面那樣繼承StaticMethodMatcherPointcut。這只需要實(shí)現(xiàn)一個(gè)抽象方法 (雖然也可以覆蓋其它方法來(lái)定制行為):

          class TestStaticPointcut extends StaticMethodMatcherPointcut {
          public boolean matches(Method m, Class targetClass) {
          // return true if custom criteria match
          }
          }

          動(dòng)態(tài)切入點(diǎn)也有很多基類。

          你可以用Spring 1.0 RC2和更高版本里的自定義切入點(diǎn)及通知類型。

          7.2.6. 自定義切入點(diǎn)

          因?yàn)樵赟pring AOP中的切入點(diǎn)是Java類而不是語(yǔ)言的特性(后者像AspectJ中那樣),所以可以聲明自定義的切入點(diǎn),不論是靜態(tài)還是動(dòng)態(tài)的。自定義切入點(diǎn)在Spring里可能很強(qiáng)大。即使這樣我們?nèi)酝扑]盡可能使用AspectJ切入點(diǎn)表達(dá)式語(yǔ)言。

          [Note] Note

          后續(xù)版本的Spring也許會(huì)提供“語(yǔ)義切入點(diǎn)”,像JAC所提供的那樣:例如,“所有方法可以修改目標(biāo)對(duì)象中實(shí)例變量”

          7.3. Spring的通知API

          現(xiàn)在讓我們看一下SPring AOP是怎樣處理通知的。

          7.3.1. 通知的生命周期

          每個(gè)通知都是一個(gè)Spring bean。一個(gè)通知實(shí)例既可以被所有被通知的對(duì)象共享,也可以被每個(gè)被通知對(duì)象獨(dú)占。 這根據(jù)設(shè)置類共享(per-class)基于實(shí)例(per-instance)的參數(shù)來(lái)決定。

          類共享通知經(jīng)常會(huì)被用到。它很適合用作通用的通知例如事務(wù)advisor。這些advisor不依賴于代理對(duì)象的狀態(tài)也不會(huì)向代理對(duì)象添加新的狀態(tài);它們僅僅在方法和參數(shù)上起作用。

          基于實(shí)例的通知很適合用作導(dǎo)入器來(lái)支持混合類型。在這種情況下,通知向代理對(duì)象添加狀態(tài)。

          在同一個(gè)AOP代理里混合使用類共享和基于實(shí)例的通知是可能的。

          7.3.2. Spring里的通知類型

          Spring提供了多種開(kāi)箱即用的通知類型,而且它們也可以被擴(kuò)展來(lái)支持任何通知類型。讓我們先看看基本概念和標(biāo)準(zhǔn)的通知類型。

          7.3.2.1. 攔截around通知

          在Spring中最基礎(chǔ)的通知類型是攔截around通知

          Spring里使用方法攔截的around通知兼容AOP聯(lián)盟接口。實(shí)現(xiàn)around通知的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)的返回值。

          一個(gè)簡(jiǎn)單的MethodInterceptor實(shí)現(xiàn)看起來(lái)像下面這樣:

          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;
          }
          }

          注意對(duì)MethodInvocation中proceed()方法的調(diào)用。這個(gè)方法 繼續(xù)運(yùn)行指向連接點(diǎn)的攔截器鏈并返回proceed()的結(jié)果。然而,一個(gè)類似任何環(huán)繞通知的MethodInterceptor, 可以返回一個(gè)不同的值或者拋出一個(gè)異常而不是調(diào)用proceed方法。但除非你有很好的理由,否則不要考慮這樣做!

          [Note] Note

          MethodInterceptor提供了與其它AOP聯(lián)盟兼容實(shí)現(xiàn)的互操作性。本節(jié)的剩下部分將討論其它的通知類型,它們實(shí)現(xiàn)了通用的AOP概念, 但是以一種Spring風(fēng)格的方式來(lái)實(shí)現(xiàn)的。使用最通用的通知類型還有一個(gè)好處,固定使用MethodInterceptor 環(huán)繞通知可以讓你在其它的AOP框架里 運(yùn)行你所定制的方面。要注意現(xiàn)在切入點(diǎn)還不能和其它框架進(jìn)行互操作,AOP聯(lián)盟目前還沒(méi)有定義切入點(diǎn)接口。

          7.3.2.2. 前置通知

          一個(gè)更簡(jiǎn)單的通知類型是before 通知。它不需要 MethodInvocation對(duì)象,因?yàn)樗皇窃谶M(jìn)入方法之前被調(diào)用。

          前置通知(before advice)的一個(gè)主要優(yōu)點(diǎn)是它不需要調(diào)用proceed() 方法,因此就不會(huì)發(fā)生 無(wú)意間運(yùn)行攔截器鏈?zhǔn)〉那闆r。

          MethodBeforeAdvice 接口被顯示在下面。(Spring的API設(shè)計(jì)能夠?yàn)轭愔械某蓡T變量提供前置通知,雖然這可以把通用對(duì)象應(yīng)用到成員變量攔截上,但看起來(lái)Spring并不打算實(shí)現(xiàn)這個(gè)功能。)

          public interface MethodBeforeAdvice extends BeforeAdvice {
          void before(Method m, Object[] args, Object target) throws Throwable;
          }

          注意返回值的類型是void。前置通知可以在連接點(diǎn)執(zhí)行之前插入自定義行為,但是不能修改連接點(diǎn)的返回值。如果一個(gè)前置通知拋出異常,這將中止攔截器鏈的進(jìn)一步執(zhí)行。 異常將沿著攔截器鏈向回傳播。如果異常是非強(qiáng)制檢查的(unchecked)或者已經(jīng)被包含在被調(diào)用方法的簽名中(譯者:即出現(xiàn)在方法聲明的throws子句中),它將被直接返回給客戶端; 否則它將由AOP代理包裝在一個(gè)非強(qiáng)制檢查異常中返回。

          這里是Spring里一個(gè)前置通知的例子,它計(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;
          }
          }
          [Tip] Tip

          前置通知可以和任何切入點(diǎn)一起使用。

          7.3.2.3. 異常通知

          如果連接點(diǎn)拋出異常,異常通知(throws advice)將在連接點(diǎn)返回后被調(diào)用。 Spring提供類型檢查的異常通知,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法:它只是一個(gè)標(biāo)記接口用來(lái)標(biāo)識(shí) 所給對(duì)象實(shí)現(xiàn)了一個(gè)或者多個(gè)針對(duì)特定類型的異常通知方法。這些方法應(yīng)當(dāng)滿足下面的格式

          afterThrowing([Method], [args], [target], subclassOfThrowable) 

          只有最后一個(gè)參數(shù)是必須的。因此異常通知方法對(duì)方法及參數(shù)的需求,方法的簽名將從一到四個(gè)參數(shù)之間變化。 下面是一些throws通知的例子。

          當(dāng)一個(gè)RemoteException(包括它的子類)被拋出時(shí),下面的通知會(huì)被調(diào)用:

          public class RemoteThrowsAdvice implements ThrowsAdvice {
          public void afterThrowing(RemoteException ex) throws Throwable {
          // Do something with remote exception
          }
          }

          當(dāng)一個(gè)ServletException被拋出,下面的通知將被調(diào)用。 和上面的通知不同,它聲明了4個(gè)參數(shù),因此它可以訪問(wèn)被調(diào)用的方法,方法的參數(shù)以及目標(biāo)對(duì)象:

          public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
          public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
          // Do something will all arguments
          }
          }

          最后一個(gè)例子說(shuō)明怎樣在同一個(gè)類里使用兩個(gè)方法來(lái)處理 RemoteExceptionServletException。可以在一個(gè)類里組合任意數(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] Tip

          異常通知可以和任何切入點(diǎn)一起使用。

          7.3.2.4. 后置通知

          Spring中的一個(gè)后置通知(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;
          }

          一個(gè)后置通知可以訪問(wèn)返回值(但不能進(jìn)行修改),被調(diào)用方法,方法參數(shù)以及目標(biāo)對(duì)象。

          下面的后置通知計(jì)算所有運(yùn)行成功(沒(méi)有拋出異常)的方法調(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;
          }
          }

          這個(gè)通知不改變執(zhí)行路線。如果通知拋出異常,異常將沿著攔截器鏈返回(拋出)而不是返回被調(diào)用方法的執(zhí)行結(jié)果。

          [Tip] Tip

          后置通知可以和任何切入點(diǎn)一起使用。

          7.3.2.5. 引入通知

          Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進(jìn)行處理。

          引入通知需要一個(gè)IntroductionAdvisor, 和一個(gè)IntroductionInterceptor, 后者實(shí)現(xiàn)下面的接口:

          public interface IntroductionInterceptor extends MethodInterceptor {
          boolean implementsInterface(Class intf);
          }

          invoke() 方法,繼承了AOP聯(lián)盟MethodInterceptor 接口,必須確保實(shí)現(xiàn)引入: 這里的意思是說(shuō),如果被調(diào)用的方法位于一個(gè)已經(jīng)被引入接口里,這個(gè)引入攔截器將負(fù)責(zé)完成對(duì)這個(gè)方法的調(diào)用--因?yàn)楹笳卟荒苷{(diào)用proceed()方法。

          引入通知不能和任何切入點(diǎn)一起使用,因?yàn)樗菓?yīng)用在類級(jí)別而不是方法級(jí)別。 你可以通過(guò)IntroductionAdvisor來(lái)使用引入通知,這個(gè)接口包括下面的方法:

          public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
          ClassFilter getClassFilter();
          void validateInterfaces() throws IllegalArgumentException;
          }
          public interface IntroductionInfo {
          Class[] getInterfaces();
          }

          這里沒(méi)有MethodMatcher接口,因此也就沒(méi)有 Pointcut接口與引入通知相關(guān)聯(lián)。這里只進(jìn)行類過(guò)濾。

          getInterfaces()方法返回這個(gè)advisor所引入的接口。

          validateInterfaces()方法將被內(nèi)部使用來(lái)查看被引入的接口是否能夠由配置的IntroductionInterceptor來(lái)實(shí)現(xiàn)。

          讓我們看看從Spring測(cè)試集里拿來(lái)的一個(gè)簡(jiǎn)單例子。讓我們假設(shè)我們希望把下面的接口引入給一個(gè)或者多個(gè)對(duì)象:

           

          public interface Lockable {
          void lock();
          void unlock();
          boolean locked();
          }

           

          這里描述了一個(gè)混合類型。我們希望不論原本對(duì)象是什么類型,都把這個(gè)被通知對(duì)象轉(zhuǎn)換為L(zhǎng)ockable接口并可以調(diào)用lock 和unlock 方法。 如果我們調(diào)用lock() 方法,我們希望所有的setter 方法拋出一個(gè)LockedException異常。這樣我們就可以加入一個(gè)方面來(lái)確保對(duì)象在得到通知之前是不可修改的:一個(gè)關(guān)于AOP的好例子。

          首先,我們需要一個(gè)IntroductionInterceptor來(lái)做粗活。這里,我們擴(kuò)展了 org.springframework.aop.support.DelegatingIntroductionInterceptor這個(gè)方便的類。我們能夠直接實(shí)現(xiàn) IntroductionInterceptor接口,但在這個(gè)例子里使用DelegatingIntroductionInterceptor是最好的選擇。

          DelegatingIntroductionInterceptor設(shè)計(jì)為把一個(gè)引入托管給一個(gè)實(shí)現(xiàn)這個(gè)接口的類, 這通過(guò)隱藏?cái)r截的使用來(lái)實(shí)現(xiàn)。托管可以被設(shè)置到任何具有構(gòu)造器方法的類;這里使用缺省托管(即使用無(wú)參構(gòu)造器)。 因此在下面這個(gè)例子里,托管者將是DelegatingIntroductionInterceptor的子類 LockMixin。 當(dāng)一個(gè)托管實(shí)現(xiàn)被提供,DelegatingIntroductionInterceptor實(shí)例將查找托管所實(shí)現(xiàn)的所有接口 (除了IntroductionInterceptor之外),并為這些接口的介紹提供支持。子類例如LockMixin 可以調(diào)用suppressInterface(Class intf) 方法來(lái)禁止那些不應(yīng)該被暴露的接口。 然而,不論IntroductionInterceptor支持多少接口, IntroductionAdvisor的使用將控制哪些接口真正被暴露。 一個(gè)被引入的接口將覆蓋目標(biāo)對(duì)象實(shí)現(xiàn)的相同接口.

          這樣LockMixin就繼承了DelegatingIntroductionInterceptor并實(shí)現(xiàn)了Lockable 接口本身。 這里父類會(huì)自動(dòng)選擇Lockable接口并提供引入支持,因此我們不需要配置它。用這種方法我們能夠介紹任意數(shù)量的接口。

          注意locked實(shí)例變量的用法。這有效地向目標(biāo)對(duì)象增加了額外狀態(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)包含了一個(gè)實(shí)現(xiàn)--如果一個(gè)方法被引入,這個(gè)實(shí)現(xiàn)將調(diào)用實(shí)際的托管方法,否則它將直接處理連接點(diǎn)--通常這已經(jīng)足夠了。在當(dāng)前這個(gè)例子里,我們需要增加一個(gè)檢查:如果處于加鎖(locked)狀態(tài),沒(méi)有setter方法可以被調(diào)用。

          引入處理器的要求是很簡(jiǎn)單的。它的全部要求只是保持一個(gè)特定的LockMixin實(shí)例, 并說(shuō)明被通知的接口--在這個(gè)例子里,只有一個(gè)Lockable接口。 一個(gè)更復(fù)雜的例子也許會(huì)獲取一個(gè)介紹攔截器的引用(后者可以被定義為一個(gè)prototype): 在這種情況下,不需要對(duì)LockMixin進(jìn)行相關(guān)配置,因此我們可以簡(jiǎn)單的用new關(guān)鍵字來(lái)創(chuàng)建它。

           

          public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
          public LockMixinAdvisor() {
          super(new LockMixin(), Lockable.class);
          }
          }

           

          我們可以很容易應(yīng)用這個(gè)advisor:它不需要配置。(然而,下面必須記住的:不可以在沒(méi)有IntroductionAdvisor的情況下使用IntroductionInterceptor。) 對(duì)于通常的引入advisor必須是基于實(shí)例的,因?yàn)樗怯袪顟B(tài)的。因此,對(duì)于每個(gè)被通知對(duì)象我們需要一個(gè)不同 實(shí)例的LockMixinAdvisorLockMixin。這種情況下advisor保存了被通知對(duì)象的部分狀態(tài)。

          我們能夠通過(guò)使用Advised.addAdvisor() 的編程方式來(lái)應(yīng)用advisor,或者像其它advisor那樣(也是推薦的方式)在XML里進(jìn)行配置。全部的代理創(chuàng)建選擇(包括“自動(dòng)代理創(chuàng)建器”)將在下面進(jìn)行討論, 看看如何正確地處理introduction和有狀態(tài)混合類型。

          7.4. Spring里的advisor(Advisor) API

          在Spring里,一個(gè)advisor是一個(gè)僅僅包含一個(gè)通知對(duì)象和與之關(guān)聯(lián)的切入點(diǎn)表達(dá)式的切面。

          除了引入這種特殊形式,任何advisor都可以和任何通知一起工作。 org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor類。例如,它可以和: MethodInterceptorBeforeAdvice 或者 ThrowsAdvice一起使用。

          在Spring里有可能在同一個(gè)AOP代理里模糊advisor和通知類型。例如,你可以在一個(gè)代理配置里使用一個(gè)interception環(huán)繞通知,一個(gè)異常通知和一個(gè)前置通知:Spring將負(fù)責(zé)自動(dòng)創(chuàng)建所需的攔截器鏈。

          7.5. 使用ProxyFactoryBean創(chuàng)建AOP代理

          如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來(lái)管理你的業(yè)務(wù)對(duì)象--這正是你應(yīng)該做的--你也許會(huì)想要使用Spring中關(guān)于AOP的FactoryBean。(記住使用工廠bean引入一個(gè)間接層之后,我們就可以創(chuàng)建不同類型的對(duì)象了)。

          [Note] Note

          Spring 2.0的AOP支持也在底層使用工廠bean。

          在Spring里創(chuàng)建一個(gè)AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個(gè)類對(duì)應(yīng)用的切入點(diǎn)和通知提供了完整的控制能力(包括它們的應(yīng)用順序)。然而如果你不需要這種控制,你會(huì)喜歡更簡(jiǎn)單的方式。

          7.5.1. 基礎(chǔ)

          像其它的FactoryBean實(shí)現(xiàn)一樣,ProxyFactoryBean引入了一個(gè)間接層。如果你定義一個(gè)名為fooProxyFactoryBean, 引用foo的對(duì)象看到的將不是ProxyFactoryBean實(shí)例本身,而是一個(gè)ProxyFactoryBean實(shí)現(xiàn)里getObject() 方法所創(chuàng)建的對(duì)象。 這個(gè)方法將創(chuàng)建一個(gè)AOP代理,它包裝了一個(gè)目標(biāo)對(duì)象。

          使用ProxyFactoryBean或者其它IoC相關(guān)類帶來(lái)的最重要的好處之一就是創(chuàng)建AOP代理,這意味著通知和切入點(diǎn)也可以由IoC來(lái)管理。這是一個(gè)強(qiáng)大的功能并使得某些特定的解決方案成為可能, 而這些用其它AOP框架很難做到。例如,一個(gè)通知也許本身也要引用應(yīng)用程序?qū)ο螅ú粌H僅是其它AOP框架中也可以訪問(wèn)的目標(biāo)對(duì)象),這令你可以從依賴注射的可拔插特性中獲益。

          7.5.2. JavaBean屬性

          通常情況下Spring提供了大多數(shù)的FactoryBean實(shí)現(xiàn),ProxyFactoryBean類本身也是一個(gè)JavaBean。它的屬性被用來(lái):

          一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(lái)(這個(gè)類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:

          • proxyTargetClass:這個(gè)屬性為true時(shí),目標(biāo)類本身被代理而不是目標(biāo)類的接口。如果這個(gè)屬性值被設(shè)為true,CGLIB代理將被創(chuàng)建(可以參看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。

          • optimize:用來(lái)控制通過(guò)CGLIB創(chuàng)建的代理是否使用激進(jìn)的優(yōu)化策略。除非完全了解AOP代理如何處理優(yōu)化,否則不推薦用戶使用這個(gè)設(shè)置。目前這個(gè)屬性僅用于CGLIB代理;對(duì)于JDK動(dòng)態(tài)代理(缺省代理)無(wú)效。

          • frozen:用來(lái)控制代理工廠被配置之后,是否還允許修改通知。缺省值為false(即在代理被配置之后,不允許修改代理的配置)。

          • exposeProxy:決定當(dāng)前代理是否被保存在一個(gè)ThreadLocal中以便被目標(biāo)對(duì)象訪問(wèn)。(目標(biāo)對(duì)象本身可以通過(guò)MethodInvocation來(lái)訪問(wèn),因此不需要ThreadLocal。) 如果個(gè)目標(biāo)對(duì)象需要獲取代理而且exposeProxy屬性被設(shè)為true,目標(biāo)對(duì)象可以使用AopContext.currentProxy()方法。

          • aopProxyFactory:使用AopProxyFactory的實(shí)現(xiàn)。這提供了一種方法來(lái)自定義是否使用動(dòng)態(tài)代理,CGLIB或其它代理策略。 缺省實(shí)現(xiàn)將根據(jù)情況選擇動(dòng)態(tài)代理或者CGLIB。一般情況下應(yīng)該沒(méi)有使用這個(gè)屬性的需要;它是被設(shè)計(jì)來(lái)在Spring 1.1中添加新的代理類型的。

          ProxyFactoryBean中需要說(shuō)明的其它屬性包括:

          • proxyInterfaces:需要代理的接口名的字符串?dāng)?shù)組。如果沒(méi)有提供,將為目標(biāo)類使用一個(gè)CGLIB代理(也可以查看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。

          • interceptorNamesAdvisor的字符串?dāng)?shù)組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優(yōu)先服務(wù)。就是說(shuō)列表里的第一個(gè)攔截器將能夠第一個(gè)攔截調(diào)用。

            這里的名字是當(dāng)前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因?yàn)檫@會(huì)導(dǎo)致ProxyFactoryBean忽略通知的單例設(shè)置。

            你可以把一個(gè)攔截器的名字加上一個(gè)星號(hào)作為后綴(*)。這將導(dǎo)致這個(gè)應(yīng)用程序里所有名字以星號(hào)之前部分開(kāi)頭的advisor都被應(yīng)用。你可以在Section 7.5.6, “使用“全局”advisor” 發(fā)現(xiàn)一個(gè)使用這個(gè)特性的例子。

          • 單例:工廠是否應(yīng)該返回同一個(gè)對(duì)象,不論方法getObject()被調(diào)用的多頻繁。多個(gè)FactoryBean實(shí)現(xiàn)都提供了這個(gè)方法。缺省值是true。如果你希望使用有狀態(tài)的通知--例如,有狀態(tài)的mixin--可以把單例屬性的值設(shè)置為false來(lái)使用原型通知。

          7.5.3. 基于JDK和CGLIB的代理

          這個(gè)小節(jié)作為說(shuō)明性文檔,解釋了對(duì)于一個(gè)目標(biāo)對(duì)象(需要被代理的對(duì)象),ProxyFactryBean是如何決定究竟創(chuàng)建一個(gè)基于JDK還是CGLIB的代理的。

          [Note] Note

          ProxyFactoryBean需要?jiǎng)?chuàng)建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。現(xiàn)在ProxyFactoryBean在關(guān)于自動(dòng)檢測(cè)接口方面使用了與TransactionProxyFactoryBean相似的語(yǔ)義。

          如果一個(gè)需要被代理的目標(biāo)對(duì)象的類(后面將簡(jiǎn)單地稱它為目標(biāo)類)沒(méi)有實(shí)現(xiàn)任何接口,那么一個(gè)基于CGLIB的代理將被創(chuàng)建。這是最簡(jiǎn)單的場(chǎng)景,因?yàn)镴DK代理是基于接口的,沒(méi)有接口意味著沒(méi)有使用JDK進(jìn)行代理的可能。 在目標(biāo)bean里將被插入探測(cè)代碼,通過(guò)interceptorNames屬性給出了攔截器的列表。注意一個(gè)基于CGLIB的代理將被創(chuàng)建即使ProxyFactoryBeanproxyTargetClass屬性被設(shè)置為false。 (很明顯這種情況下對(duì)這個(gè)屬性進(jìn)行設(shè)置是沒(méi)有意義的,最好把它從bean的定義中移除,因?yàn)殡m然這只是個(gè)多余的屬性,但在許多情況下會(huì)引起混淆。)

          如果目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口,那么創(chuàng)建代理的類型將根據(jù)ProxyFactoryBean的配置來(lái)決定。

          如果ProxyFactoryBeanproxyTargetClass屬性被設(shè)為true,那么一個(gè)基于CGLIB的代理將創(chuàng)建。這樣的規(guī)定是有意義的,遵循了最小驚訝法則(保證了設(shè)定的一致性)。 甚至當(dāng)ProxyFactoryBeanproxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名,而proxyTargetClass屬性被設(shè)置為true仍然實(shí)際使用基于CGLIB的代理。

          如果ProxyFactoryBeanproxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名,一個(gè)基于JDK的代理將被創(chuàng)建。被創(chuàng)建的代理將實(shí)現(xiàn)所有在proxyInterfaces屬性里被說(shuō)明的接口;如果目標(biāo)類實(shí)現(xiàn)了全部在proxyInterfaces屬性里說(shuō)明的接口以及一些額外接口,返回的代理將只實(shí)現(xiàn)說(shuō)明的接口而不會(huì)實(shí)現(xiàn)那些額外接口。

          如果ProxyFactoryBeanproxyInterfaces屬性沒(méi)有被設(shè)置,但是目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口,那么ProxyFactoryBean將自動(dòng)檢測(cè)到這個(gè)目標(biāo)類已經(jīng)實(shí)現(xiàn)了至少一個(gè)接口, 一個(gè)基于JDK的代理將被創(chuàng)建。被實(shí)際代理的接口將是目標(biāo)類所實(shí)現(xiàn)的全部接口;實(shí)際上,這和在proxyInterfaces屬性中列出目標(biāo)類實(shí)現(xiàn)的每個(gè)接口的情況是一樣的。然而,這將顯著地減少工作量以及輸入錯(cuò)誤的可能性。

          7.5.4. 對(duì)接口進(jìn)行代理

          讓我們看一個(gè)關(guān)于ProxyFactoryBean的簡(jiǎn)單例子。這個(gè)例子涉及:

          • 一個(gè)將被代理的目標(biāo)bean。在下面的例子里這個(gè)bean是“personTarget”。

          • 被用來(lái)提供通知的一個(gè)advisor和一個(gè)攔截器。

          • 一個(gè)AOP代理bean的定義,它說(shuō)明了目標(biāo)對(duì)象(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,前置, 后置和異常通知對(duì)象都可以在這里被使用。這里advisor的順序是很重要的。

          [Note] Note

          你也許很奇怪為什么這個(gè)列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設(shè)置為false,它必須返回獨(dú)立的代理實(shí)例。如果任何advisor本身是一個(gè)原型,則每次都返回一個(gè)獨(dú)立實(shí)例,因此它必須能夠從工廠里獲得原型的一個(gè)實(shí)例;保存一個(gè)引用是不夠的。

          上面“person” bean的定義可以被用來(lái)取代一個(gè)Person接口的實(shí)現(xiàn),就像下面這樣:

          Person person = (Person) factory.getBean("person");

          在同一個(gè)IoC上下文中其它的bean可以對(duì)這個(gè)bean有基于類型的依賴,就像對(duì)一個(gè)普通的Java對(duì)象那樣:

          <bean id="personUser" class="com.mycompany.PersonUser">
          <property name="person"><ref local="person" /></property>
          </bean>

          這個(gè)例子里的PersonUser類將暴露一個(gè)類型為Person的屬性。就像我們關(guān)心的那樣,AOP代理可以透明地取代一個(gè)“真實(shí)”的person接口實(shí)現(xiàn)。然而,它的類將是一個(gè)動(dòng)態(tài)代理類。 它可以被轉(zhuǎn)型成Advised接口(將在下面討論)。

          就像下面這樣,你可以使用一個(gè)匿名內(nèi)部bean來(lái)隱藏目標(biāo)和代理之間的區(qū)別。僅僅ProxyFactoryBean的定義有所不同;通知的定義只是由于完整性的原因而被包括進(jìn)來(lái):

          <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>

          對(duì)于只需要一個(gè)Person類型對(duì)象的情況,這是有好處的:如果你希望阻止應(yīng)用程序上下文的用戶獲取一個(gè)指向未通知對(duì)象的引用或者希望避免使用Spring IoC 自動(dòng)織入 時(shí)的混淆。 按理說(shuō)ProxyFactoryBean定義還有一個(gè)優(yōu)點(diǎn)是它是自包含的。然而,有時(shí)能夠從工廠里獲取未通知的目標(biāo)也是一個(gè)優(yōu)點(diǎn):例如,在某些測(cè)試場(chǎng)景里。

          7.5.5. 對(duì)類進(jìn)行代理

          如果你需要代理一個(gè)類而不是代理一個(gè)或是更多接口,那么情況將是怎樣?

          想象在我們上面的例子里,不存在Person接口:我們需要通知一個(gè)叫做Person的類,它沒(méi)有實(shí)現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動(dòng)態(tài)代理。 這只需簡(jiǎn)單地把上面ProxyFactoryBean的proxyTargetClass屬性設(shè)為true。雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時(shí),通知沒(méi)有實(shí)現(xiàn)接口的類的能力是非常有用的。(通常情況下,Spring沒(méi)有任何規(guī)定。它只是讓你很容易根據(jù)實(shí)際情況選擇最好的解決方案,避免強(qiáng)迫使用特定方式)。

          也許你希望你能夠在任何情況下都強(qiáng)制使用CGLIB,甚至在你使用接口的時(shí)候也這樣做。

          CGLIB通過(guò)在運(yùn)行時(shí)生成一個(gè)目標(biāo)類的子類來(lái)進(jìn)行代理工作。Spring配置這個(gè)生成的子類對(duì)原始目標(biāo)對(duì)象的方法調(diào)用進(jìn)行托管:子類實(shí)現(xiàn)了裝飾器(Decorator)模式,把通知織入。

          CGLIB的代理活動(dòng)應(yīng)當(dāng)對(duì)用戶是透明的。然而,有一些問(wèn)題需要被考慮:

          • Final方法不可以被通知,因?yàn)樗鼈儾荒鼙桓采w。

          • 你需要在你的類路徑里有CGLIB 2的庫(kù);使用動(dòng)態(tài)代理的話只需要JDK。

          在CGLIB代理和動(dòng)態(tài)代理之間的速度差別是很小的。在Spring 1.0中,動(dòng)態(tài)代理會(huì)快一點(diǎn)點(diǎn)。但這點(diǎn)可能在將來(lái)被改變。這種情況下,選擇使用何種代理時(shí)速度不應(yīng)該成為決定性的理由。

          7.5.6. 使用“全局”advisor

          通過(guò)在一個(gè)攔截器名后添加一個(gè)星號(hào),所有bean名字與星號(hào)之前部分相匹配的通知都將被加入到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"/>
          

          7.6. 簡(jiǎn)化代理定義

          你也許需要許多相似的代理定義,特別是定義事務(wù)性代理的時(shí)候。使用父子bean定義,以及內(nèi)部bean定義,可以讓代理定義大大得到極大的簡(jiǎn)化。

          首先從父bean開(kāi)始,為代理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>

          這個(gè)bean本身將永遠(yuǎn)不會(huì)被初始化,所以實(shí)際上是不完整的。而后每個(gè)需要?jiǎng)?chuàng)建的代理都是這個(gè)bean定義的子bean定義,它們把代理的目標(biāo)類包裝為一個(gè)內(nèi)部bean定義,因?yàn)槟繕?biāo)對(duì)象本身將不會(huì)被單獨(dú)使用。

          <bean id="myService" parent="txProxyTemplate">
          <property name="target">
          <bean class="org.springframework.samples.MyServiceImpl">
          </bean>
          </property>
          </bean>

          當(dāng)然你可以覆蓋從模版中繼承的屬性,例如在下面這個(gè)例子里的事務(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)明確地通過(guò)設(shè)定abstract屬性把父bean定義標(biāo)注為abstract,在前面的章節(jié)里有描述,這樣它實(shí)際上不能被初始化。 缺省情況下應(yīng)用程序上下文(不僅僅是bean工廠)將預(yù)先初始化所有的實(shí)例為單例。因此下面這點(diǎn)是很重要的(至少對(duì)于單例bean來(lái)說(shuō)),如果你有一個(gè)(父)bean定義你希望僅僅作為模版使用,而這個(gè)定義說(shuō)明了一個(gè)類,你必須把abstract參數(shù)設(shè)置為true,否則應(yīng)用程序上下文將試圖預(yù)先初始化它。

          7.7. 使用ProxyFactory通過(guò)編程創(chuàng)建AOP代理

          使用Spring通過(guò)編程創(chuàng)建AOP代理是很容易的。這使你可以使用Spring AOP而不必依賴于Spring IoC。

          下面的清單顯示了如何使用一個(gè)攔截器和一個(gè)advisor來(lái)為一個(gè)目標(biāo)對(duì)象來(lái)創(chuàng)建一個(gè)代理。目標(biāo)對(duì)象實(shí)現(xiàn)的接口將被自動(dòng)代理:

          ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
          factory.addInterceptor(myMethodInterc ptor);
          factory.addAdvisor(myAdvisor);
          MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();

          第一步是創(chuàng)建一個(gè)類型為org.springframework.aop.framework.ProxyFactory的對(duì)象。你可以像上面例子里那樣使用一個(gè)目標(biāo)對(duì)象來(lái)創(chuàng)建它,或者在一個(gè)可選的構(gòu)造器里說(shuō)明需要被代理的接口。

          你可以添加攔截器或advisor,并在ProxyFactory的生命周期里操作它們。如果你加入一個(gè)IntroductionInterceptionAroundAdvisor,你可以讓代理實(shí)現(xiàn)額外的接口。

          在ProxyFactory里也有很方便的方法(繼承自AdvisedSupport)允許你加入其它的通知類型例如前置和異常通知。AdvisedSupport是ProxyFactory 和ProxyFactoryBean的共同父類。

          [Tip] Tip

          在大多數(shù)應(yīng)用程序里,把AOP代理的創(chuàng)建和IoC框架集成是最佳實(shí)踐。通常情況下我們推薦你在Java代碼外進(jìn)行AOP的配置。

          7.8. 操作被通知對(duì)象

          在創(chuàng)建了AOP代理之后,你能夠使用org.springframework.aop.framework.Advised接口對(duì)它們進(jìn)行管理。任何AOP代理都能夠被轉(zhuǎn)型為這個(gè)接口,不論它實(shí)現(xiàn)了哪些其它接口。這個(gè)接口包括下面的方法:

          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()方法將為每個(gè)已經(jīng)被加入工廠的advisor,攔截器或者其它通知類型返回一個(gè)advisor。如果你曾經(jīng)添加一個(gè)advisor,那么所返回的advisor將是你加入的對(duì)象。 如果你曾經(jīng)加入一個(gè)攔截器或者其它通知類型,Spring將把它們包裝在一個(gè)advisor里,后者使用一個(gè)永遠(yuǎn)返回true的切入點(diǎn)。因此如果你曾經(jīng)加入一個(gè)MethodInterceptor, 返回的advisor將是一個(gè)DefaultPointcutAdvisor,它可以返回你加入的MethodInterceptor和一個(gè)匹配所有類和方法的切入點(diǎn)。

          addAdvisor()方法可以用來(lái)添加任何advisor。通常保存切入點(diǎn)和通知的advisor是DefaultPointcutAdvisor,它可以用于任何通知或切入點(diǎn)(但不包括引入類型)。

          缺省情況下,你可以加入或移除advisor或者攔截器甚至當(dāng)代理已經(jīng)被創(chuàng)建之后。唯一的限制是無(wú)法加入或者移除一個(gè)引入advisor,因?yàn)楣S中獲得的已有代理不能顯示接口的改變(你可以通過(guò)從工廠里獲取一個(gè)新的代理來(lái)避免這個(gè)問(wèn)題)。

          下面是一個(gè)簡(jiǎn)單的例子,它把一個(gè)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] Note

          在一個(gè)實(shí)際運(yùn)行的系統(tǒng)里,修改一個(gè)業(yè)務(wù)對(duì)象上的通知是否明智是個(gè)問(wèn)題,雖然無(wú)疑在某些情況下這樣做是合理的。然而這在開(kāi)發(fā)中是很有用的: 例如,在測(cè)試的時(shí)候。對(duì)于希望測(cè)試的方法調(diào)用,有時(shí)我發(fā)現(xiàn)把測(cè)試代碼加入到一個(gè)攔截器或者其它通知里是非常有用的。(例如,通知可以與目標(biāo)方法存在于同一個(gè)事務(wù)里,在把事務(wù)標(biāo)記為回滾之前可以用SQL來(lái)檢查數(shù)據(jù)庫(kù)是否被正確的更新了。)

          依賴于你怎樣創(chuàng)建代理,你通常可以設(shè)置一個(gè) frozen標(biāo)志,在這種情況下 AdvisedisFrozen()方法將返回true,任何增加或者移除通知的修改都會(huì)導(dǎo)致一個(gè)AopConfigException異常。 在某些情況下這種凍結(jié)被通知對(duì)象狀態(tài)的能力是很有用的:例如,防止調(diào)用代碼來(lái)移除一個(gè)進(jìn)行安全檢查的攔截器。在Spring 1.1中它也被用來(lái)允許激進(jìn)優(yōu)化,如果已經(jīng)知道不需要運(yùn)行時(shí)對(duì)通知進(jìn)行修改的話。

          7.9. 使用“自動(dòng)代理(autoproxy)”功能

          到目前為止我們已經(jīng)考慮了如何使用ProxyFactoryBean或者類似的工廠bean來(lái)顯式創(chuàng)建AOP代理。

          Spring也允許我們使用“自動(dòng)代理”的bean定義,可以自動(dòng)對(duì)被選中的bean定義進(jìn)行代理。這建立在Spring的“bean post processor”功能上,后者允許在容器加載時(shí)修改任何bean的定義。

          在這個(gè)模型下,你在你的XML bean定義文件中建立一些特定的bean定義來(lái)配置自動(dòng)代理功能。這允許你僅僅聲明那些將被自動(dòng)代理的適當(dāng)目標(biāo):你不需要使用ProxyFactoryBean

          有兩種方式可以做到這點(diǎn):

          • 使用一個(gè)引用當(dāng)前上下文中特定bean的自動(dòng)代理創(chuàng)建器。

          • 一個(gè)專用自動(dòng)代理的創(chuàng)建需要被單獨(dú)考慮;自動(dòng)代理創(chuàng)建由源代碼級(jí)別的元數(shù)據(jù)屬性驅(qū)動(dòng)。

          7.9.1. 自動(dòng)代理bean定義

          org.springframework.aop.framework.autoproxy包提供了下列標(biāo)準(zhǔn)自動(dòng)代理創(chuàng)建器。

          7.9.1.1. BeanNameAutoProxyCreator

          BeanNameAutoProxyCreator為名字匹配字符串或者通配符的bean自動(dòng)創(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一樣,這里有一個(gè)interceptorNames屬性而不是一個(gè)攔截器的列表,這允許使用原型(prototype)advisor。這里的“攔截器”可以是advisor或任何通知類型。

          與通常的自動(dòng)代理一樣,使用BeanNameAutoProxyCreator的主要目的是把相同的配置一致地應(yīng)用到多個(gè)對(duì)象,并且使用最少量的配置。一個(gè)流行的選擇是把聲明式事務(wù)應(yīng)用到多個(gè)對(duì)象上。

          那些名字匹配的Bean定義,例如上面的例子里的“jdkMyBean”和“onlyJdk”,本身只是目標(biāo)類的普通bean定義。一個(gè)AOP對(duì)象將被BeanNameAutoProxyCreator自動(dòng)創(chuàng)建。 相同的通知將被應(yīng)用到全部匹配的bean上。注意如果advisor被使用(而不是像上面例子里那樣使用攔截器),對(duì)于不同bean可以應(yīng)用不同的切入點(diǎn)。

          7.9.1.2. DefaultAdvisorAutoProxyCreator

          一個(gè)更加通用而且強(qiáng)大得多的自動(dòng)代理創(chuàng)建器是DefaultAdvisorAutoProxyCreator。它自動(dòng)應(yīng)用當(dāng)前上下文中適當(dāng)?shù)腶dvisor,無(wú)需在自動(dòng)代理advisor的bean定義中包括bean的名字。 比起BeanNameAutoProxyCreator,它提供了同樣關(guān)于一致性配置的優(yōu)點(diǎn)而避免了前者的重復(fù)性。

          使用這個(gè)功能將涉及:

          • 說(shuō)明一個(gè) DefaultAdvisorAutoProxyCreator的bean定義

          • 在同一個(gè)或者相關(guān)的上下文中說(shuō)明任意數(shù)量的advisor。注意這些必須是advisor而不僅僅是攔截器或者其它通知。這點(diǎn)是必要的因?yàn)楸仨氂幸粋€(gè)切入點(diǎn)被評(píng)估,以便檢查每個(gè)通知候選bean定義的合適性。

          DefaultAdvisorAutoProxyCreator將自動(dòng)評(píng)估包括在每個(gè)advisor中的切入點(diǎn),來(lái)看看它應(yīng)當(dāng)應(yīng)用哪個(gè)(如果有的話)通知到每個(gè)業(yè)務(wù)對(duì)象(例如例子里的“businessObject1”和“businessObject2”)。

          這意味著可以向每個(gè)業(yè)務(wù)對(duì)象應(yīng)用任意數(shù)量的advisor。對(duì)于一個(gè)業(yè)務(wù)對(duì)象,如果沒(méi)有任何advisor中的切入點(diǎn)匹配它的任何方法,這個(gè)對(duì)象將不會(huì)被代理。當(dāng)為新的業(yè)務(wù)對(duì)象加入bean定義時(shí),如果有必要它們將自動(dòng)被代理。

          通常自動(dòng)代理的好處是它讓調(diào)用者或者被依賴對(duì)象不能得到一個(gè)沒(méi)有通知過(guò)的對(duì)象。在這個(gè)ApplicationContext上調(diào)用getBean("businessObject1")將返回一個(gè)AOP代理,而不是目標(biāo)業(yè)務(wù)對(duì)象。(前面顯示的“內(nèi)部bean”也提供了同樣的優(yōu)點(diǎn)。)

          <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ù)對(duì)象上,DefaultAdvisorAutoProxyCreator是非常有用的。一旦框架的定義已經(jīng)完成,你可以簡(jiǎn)單地加入新的業(yè)務(wù)對(duì)象而不必包括特定的代理配置。你也可以很容易的去掉額外的切面--例如,跟蹤或者性能監(jiān)視切面--僅僅對(duì)配置作很小的修改。

          DefaultAdvisorAutoProxyCreator支持過(guò)濾(通過(guò)使用一個(gè)命名約定讓只有特定的advisor被評(píng)估,允許在同一個(gè)工廠里使用多個(gè)不同配置的AdvisorAutoProxyCreator)和排序。advisor可以實(shí)現(xiàn)org.springframework.core.Ordered接口來(lái)確保以正確的順序被應(yīng)用。 上面例子里的TransactionAttributeSourceAdvisor 有一個(gè)可配置的序號(hào)值;缺省情況下是沒(méi)有排序的。

          7.9.1.3. AbstractAdvisorAutoProxyCreator

          這是DefaultAdvisorAutoProxyCreator的父類。如果在某些情況下框架提供的DefaultAdvisorAutoProxyCreator不能滿足你的需要,你可以通過(guò)繼承這個(gè)類來(lái)創(chuàng)建你自己的自動(dòng)代理創(chuàng)建器。

          7.9.2. 使用元數(shù)據(jù)驅(qū)動(dòng)的自動(dòng)代理

          一個(gè)非常重要的自動(dòng)代理類型是由元數(shù)據(jù)驅(qū)動(dòng)的。這提供了一種和.NET ServicedComponents相似的編程模型。作為使用類似EJB里的XML描述符的替代,對(duì)于事務(wù)管理和其它企業(yè)服務(wù)的配置都將被保存在源代碼級(jí)別的屬性里。

          在這個(gè)情況下,你使用DefaultAdvisorAutoProxyCreator和可以理解元數(shù)據(jù)屬性的advisor。元數(shù)據(jù)被保存在候選advisor里的切入點(diǎn)部分,而不是在自動(dòng)代理創(chuàng)建類本身。

          這是一個(gè)DefaultAdvisorAutoProxyCreator的特殊例子,它本身沒(méi)有什么特別。(元數(shù)據(jù)的相關(guān)代碼保存在advisor內(nèi)的切入點(diǎn)里,而不是AOP框架本身)。

          JPetStore示例應(yīng)用程序的/attributes 目錄顯示了如何使用參數(shù)驅(qū)動(dòng)的自動(dòng)代理。在這個(gè)例子里,不需要使用TransactionProxyFactoryBean。因?yàn)槭褂昧嗽獢?shù)據(jù)相關(guān)的切入點(diǎn), 所以簡(jiǎn)單在業(yè)務(wù)對(duì)象上定義事務(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)用程序上下文中查找所有合適的切入點(diǎn)。在這個(gè)例子里,TransactionAttributeSourceAdvisor類型的“transactionAdvisor”bean定義將應(yīng)用到帶有一個(gè)事務(wù)屬性的類或方法上。 TransactionAttributeSourceAdvisor的構(gòu)造器依賴于一個(gè)TransactionInterceptor。這個(gè)例子里通過(guò)自動(dòng)織入解決了這個(gè)問(wèn)題。AttributesTransactionAttributeSource依賴于一個(gè)org.springframework.metadata.Attributes接口的實(shí)現(xiàn)。 在這個(gè)代碼片斷里,“attributes”bean使用Jakarta Commons Attributes API來(lái)獲取屬性信息以滿足這個(gè)要求。(應(yīng)用程序代碼必須已經(jīng)使用Commons Attribut來(lái)es的編譯任務(wù)編譯過(guò)了。)

          JPetStore示例應(yīng)用程序的 /annotation 目錄包括了一個(gè)由JDK 1.5+注解驅(qū)動(dòng)的自動(dòng)代理的模擬例子。下面的配置允許自動(dòng)檢測(cè)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依賴于一個(gè)PlatformTransactionManager定義,后者沒(méi)有被包括在這個(gè)通用的文件里(雖然它可以被包括在這里)因?yàn)樗趹?yīng)用程序的事務(wù)需求規(guī)范中指定(在這個(gè)例子里使用JTA,而在其它情況下,可以是Hibernate,JDO或者JDBC):

          <bean id="transactionManager"
          class="org.springframework.transaction.jta.JtaTransactionManager"/>
          [Tip] Tip

          如果你只需要聲明式事務(wù)管理,使用這些通用的XML定義將導(dǎo)致Spring自動(dòng)代理所有帶有事務(wù)屬性的類或者方法。你將不需要直接使用AOP工作, 這個(gè)編程模型和.NET的ServicedComponents相似。

          這個(gè)架構(gòu)是可以擴(kuò)展的。可以在自定義屬性的基礎(chǔ)上進(jìn)行自動(dòng)代理。你所需要做的是:

          • 定義你自己的自定義屬性

          • 使用必要的通知說(shuō)明一個(gè)advisor,也包括一個(gè)切入點(diǎn),后者可以被類或者方法上的自定義屬性觸發(fā)。你也許能夠使用已有的通知,而僅僅實(shí)現(xiàn)一個(gè)能夠處理自定義屬性的靜態(tài)切入點(diǎn)。

          可以讓這些advisor對(duì)于每個(gè)被通知對(duì)象(例如,mixins)都是唯一的:僅僅需要在bean定義中被定義為原型而不是單例。例如,在上面所顯示的Spring測(cè)試集中的LockMixin引入攔截器可以和一個(gè)屬性驅(qū)動(dòng)的切入點(diǎn)聯(lián)合定位一個(gè)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)的切入點(diǎn)匹配anyBean或其它bean定義里的任何方法,mixin將被應(yīng)用。注意lockMixinlockableAdvisor的定義都是原型。myAttributeAwarePointcut切入點(diǎn)可以是個(gè)單例,因?yàn)樗鼪](méi)有為單個(gè)被通知對(duì)象保持狀態(tài)。

          7.10. 使用TargetSources

          Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource接口進(jìn)行描述。這個(gè)接口負(fù)責(zé)返回一個(gè)實(shí)現(xiàn)連接點(diǎn)的“目標(biāo)對(duì)象(target object)”。每當(dāng)AOP代理處理一個(gè)方法調(diào)用時(shí)都會(huì)向TargetSource的實(shí)現(xiàn)請(qǐng)求一個(gè)目標(biāo)實(shí)例。

          使用Spring AOP的開(kāi)發(fā)者通常不需要直接和TargetSource打交道,但這提供了一種強(qiáng)大的方式來(lái)支持池化(pooling),熱交換(hot swappable)和其它高級(jí)目標(biāo)。例如,一個(gè)使用池來(lái)管理實(shí)例的TargetSource可以為每個(gè)調(diào)用返回一個(gè)不同的目標(biāo)實(shí)例。

          如果你不指定一個(gè)TargetSource,一個(gè)缺省實(shí)現(xiàn)將被使用,它包裝一個(gè)本地對(duì)象。對(duì)于每次調(diào)用它將返回相同的目標(biāo)(像你期望的那樣)。

          讓我們看看Spring提供的標(biāo)準(zhǔn)目標(biāo)源(target source)以及如何使用它們。

          [Tip] Tip

          當(dāng)使用一個(gè)自定義的目標(biāo)源,你的目標(biāo)通常需要是一個(gè)原型而不是一個(gè)單例的bean定義。這允許Spring在必要時(shí)創(chuàng)建新的目標(biāo)實(shí)例。

          7.10.1. 熱交換目標(biāo)源

          org.springframework.aop.target.HotSwappableTargetSource允許當(dāng)調(diào)用者保持引用的時(shí)候,切換一個(gè)AOP代理的目標(biāo)。

          修改目標(biāo)源的目標(biāo)將立即生效。 HotSwappableTargetSource是線程安全的。

          你可以通過(guò)HotSwappableTargetSource的 swap()方法來(lái)改變目標(biāo),就像下面那樣:

          HotSwappableTargetSource swapper =
          (HotSwappableTargetSource) beanFactory.getBean("swapper");
          Object oldTarget = swapper.swap(newTarget);

          所需的XML定義看起來(lái)像下面這樣:

          <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)。保持對(duì)這個(gè)bean的引用的客戶將不知道發(fā)生了這個(gè)修改,但是將可以立即點(diǎn)擊新的目標(biāo)。

          這個(gè)例子沒(méi)有添加任何通知--也不必為使用一個(gè)TargetSource添加任何通知--當(dāng)然任何TargetSource都可以與任意通知聯(lián)合使用。

          7.10.2. 池化目標(biāo)源

          使用一個(gè)池化目標(biāo)源提供了和無(wú)狀態(tài)session EJB類似的編程模型,它維護(hù)一個(gè)包括相同實(shí)例的池,方法調(diào)用結(jié)束后將把對(duì)象釋放回池中。

          Spring池化和SLSB池化之間的一個(gè)決定性區(qū)別是Spring池化功能可以用于任何POJO。就像Spring通常情況下那樣,這個(gè)服務(wù)是非侵入式的。

          Spring對(duì)Jakarta Commons Pool 1.3提供了開(kāi)箱即用的支持,后者提供了一個(gè)相當(dāng)有效的池化實(shí)現(xiàn)。要使用這個(gè)特性,你需要在應(yīng)用程序路徑中存在commons-pool的Jar文件。 也可以通過(guò)繼承org.springframework.aop.target.AbstractPoolingTargetSource來(lái)支持其它的池化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)對(duì)象--例子里的“businessObjectTarget”--必須是個(gè)原型。這允許PoolingTargetSource的實(shí)現(xiàn)在必要時(shí)為目標(biāo)創(chuàng)建新的實(shí)例來(lái)增大池的容量。查看AbstractPoolingTargetSource和你想要使用的具體子類的Javadoc獲取更多關(guān)于它屬性的信息:maxSize是最基礎(chǔ)的,而且永遠(yuǎn)都要求被提供。

          在這個(gè)例子里,“myInterceptor”是一個(gè)攔截器的名字,這個(gè)攔截器需要在同一個(gè)IoC上下文中被定義。然而,定義對(duì)攔截器進(jìn)行池化是不必要的。如果你想要的只是池化而沒(méi)有其它通知,就不要設(shè)置interceptorNames屬性。

          可以配置Spring來(lái)把任何被池化對(duì)象轉(zhuǎn)型到org.springframework.aop.target.PoolingConfig接口,這通過(guò)一個(gè)introduction暴露配置以及當(dāng)前池的大小。你需要像這樣定義一個(gè)advisor:

          <bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
          <property name="targetObject" ref="poolTargetSource"/>
          <property name="targetMethod" value="getPoolingConfigMixin"/>
          </bean>

          這個(gè)advisor可以通過(guò)調(diào)用AbstractPoolingTargetSource類上的一個(gè)方便的方法來(lái)獲得,因此這里使用MethodInvokingFactoryBean。這個(gè)advisor名(這里是“poolConfigAdvisor”)必須在提供被池化對(duì)象的ProxyFactoryBean里的攔截器名列表里中。

          轉(zhuǎn)型看起來(lái)像這樣:

          PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
          System.out.println("Max pool size is " + conf.getMaxSize());
          [Note] Note

          池化無(wú)狀態(tài)服務(wù)對(duì)象通常是不必要的。我們不認(rèn)為這(池化)應(yīng)當(dāng)是缺省的選擇,因?yàn)槎鄶?shù)無(wú)狀態(tài)對(duì)象是先天線程安全的,如果資源被緩存,那么對(duì)實(shí)例進(jìn)行池化會(huì)引起很多問(wèn)題。

          使用自動(dòng)代理時(shí)池化更加簡(jiǎn)單。可以為任何自動(dòng)代理創(chuàng)建器設(shè)置所使用的TargetSource

          7.10.3. 原型目標(biāo)源

          建立一個(gè)“原型”目標(biāo)源和池化TargetSource很相似。在這個(gè)例子里,當(dāng)每次方法調(diào)用時(shí),將創(chuàng)建一個(gè)目標(biāo)的新實(shí)例。雖然在新版本的JVM中創(chuàng)建一個(gè)新對(duì)象的代價(jià)并不高,但是把新對(duì)象織入(滿足它的IoC依賴)可能是很昂貴的。因此如果沒(méi)有很好的理由,你不應(yīng)該使用這個(gè)方法。

          為了做到這點(diǎn),你可以把上面的poolTargetSource定義修改成下面的形式。(為了清楚說(shuō)明,修改了bean的名字。)

          <bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
          <property name="targetBeanName" ref="businessObjectTarget"/>
          </bean>

          這里只有一個(gè)屬性:目標(biāo)bean的名字。TargetSource的實(shí)現(xiàn)使用繼承來(lái)確保命名的一致性。就像池化目標(biāo)源那樣,目標(biāo)bean必須是一個(gè)原型的bean定義。

          7.10.4. ThreadLocal目標(biāo)源

          如果你需要為每個(gè)進(jìn)來(lái)的請(qǐng)求(即每個(gè)線程)創(chuàng)建一個(gè)對(duì)象,ThreadLocal目標(biāo)源是很有用的。 ThreadLocal的概念提供了一個(gè)JDK范圍的功能,這可以為一個(gè)線程透明的保存資源。建立一個(gè) ThreadLocalTargetSource的過(guò)程和其它目標(biāo)源幾乎完全一樣:

          <bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
          <property name="targetBeanName" value="businessObjectTarget"/>
          </bean>
          [Note] Note

          如果不正確的在一個(gè)多線程和多類加載器的環(huán)境里使用ThreadLocal,將帶來(lái)嚴(yán)重的問(wèn)題(可能潛在地導(dǎo)致內(nèi)存泄漏)。永遠(yuǎn)記住應(yīng)該把一個(gè)threadlocal包裝在其它的類里,并永遠(yuǎn)不要直接使用ThreadLocal本身(當(dāng)然是除了threadlocal包裝類之外)。 同時(shí),永遠(yuǎn)記住正確的設(shè)置(set)和取消(unset)(后者僅僅需要調(diào)用ThreadLocal.set(null))綁定到線程的本地資源。取消在任何情況下都應(yīng)該進(jìn)行,否則也許會(huì)導(dǎo)致錯(cuò)誤的行為。Spring的ThreadLocal支持將為你處理這個(gè)問(wèn)題,所以如果沒(méi)有其它正確的處理代碼,永遠(yuǎn)應(yīng)該考慮使用這個(gè)功能。

          7.11. 定義新的通知類型

          Spring AOP被設(shè)計(jì)為可擴(kuò)展的。通過(guò)在內(nèi)部使用攔截實(shí)現(xiàn)策略,你可以支持已有的環(huán)繞通知,前置通知,異常通知和后置通知之外的任意通知類型,它是開(kāi)箱即用的。

          org.springframework.aop.framework.adapter包是一個(gè)SPI包,它允許添加新的自定義通知類型而無(wú)需修改核心框架。對(duì)于自定義Advice類型的唯一的限制是它必須實(shí)現(xiàn)org.aopalliance.aop.Advice這個(gè)標(biāo)記接口。

          請(qǐng)參考org.springframework.aop.framework.adapter包的Javadoc來(lái)了解進(jìn)一步的信息。

          7.12. 更多資源

          可以參考Spring的示例應(yīng)用程序來(lái)查看Spring AOP的更多例子:

          • JPetStore的缺省配置說(shuō)明了如何使用TransactionProxyFactoryBean來(lái)進(jìn)行聲明式事務(wù)管理。

          • JPetStore的/attributes目錄說(shuō)明了如何使用屬性驅(qū)動(dòng)的聲明性事務(wù)管理。

          posted on 2007-09-07 09:19 劉錚 閱讀(697) 評(píng)論(0)  編輯  收藏 所屬分類: Spring

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導(dǎo)航

          統(tǒng)計(jì)

          留言簿(1)

          文章分類(141)

          文章檔案(147)

          搜索

          最新評(píng)論

          主站蜘蛛池模板: 滦南县| 闽清县| 洛宁县| 清镇市| 南部县| 揭阳市| 阆中市| 墨竹工卡县| 福安市| 墨江| 桃源县| 隆安县| 独山县| 中西区| 宁安市| 大名县| 页游| 柯坪县| 九龙县| 开封市| 靖江市| 舟曲县| 老河口市| 金山区| 东乡| 齐河县| 东丽区| 广东省| 交城县| 监利县| 景东| 荆州市| 墨脱县| 昌黎县| 衡南县| 南溪县| 洛川县| 湘西| 马边| 阜新| 原平市|