日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Spring AOP: Spring之面向方面編程

系統(tǒng) 1981 0

第?5?章?Spring AOP: Spring之面向方面編程

5.1.?概念

面向方面編程 ( AOP ) 提供從另一個角度來考慮程序結(jié)構(gòu)以完善面向?qū)ο缶幊蹋∣OP)。 面向?qū)ο髮?yīng)用程序分解成 各個層次的對象,而AOP將程序分解成各個 方面 或者說 關(guān)注點 。 這使得可以模塊化諸如事務(wù)管理等這些橫切多個對象的關(guān)注點。(這些關(guān)注點術(shù)語稱作 橫切 關(guān)注點。)

Spring的一個關(guān)鍵組件就是 AOP框架 。 Spring IoC容器(BeanFactory 和ApplicationContext)并不依賴于AOP, 這意味著如果你不需要使用,AOP你可以不用,AOP完善了Spring IoC,使之成為一個有效的中間件解決方案,。

AOP在Spring中的使用:

  • 提供聲明式企業(yè)服務(wù),特別是作為EJB聲明式服務(wù)的替代品。這些服務(wù)中最重要的是 聲明式事務(wù)管理 ,這個服務(wù)建立在Spring的事務(wù)管理抽象之上。

  • 允許用戶實現(xiàn)自定義的方面,用AOP完善他們的OOP的使用。

這樣你可以把Spring AOP看作是對Spring的補充,它使得Spring不需要EJB就能提供聲明式事務(wù)管理;或者 使用Spring AOP框架的全部功能來實現(xiàn)自定義的方面。

如果你只使用通用的聲明式服務(wù)或者預(yù)先打包的聲明式中間件服務(wù)如pooling,你可以不直接使用 Spring AOP,并且跳過本章的大部分內(nèi)容.

5.1.1.?AOP概念

讓我們從定義一些重要的AOP概念開始。這些術(shù)語不是Spring特有的。不幸的是,Spring的術(shù)語 不是特別地直觀。而且,如果Spring使用自己的術(shù)語,這將使人更加迷惑。

  • 方面(Aspect) : 一個關(guān)注點的模塊化,這個關(guān)注點實現(xiàn)可能 另外橫切多個對象。事務(wù)管理是J2EE應(yīng)用中一個很好的橫切關(guān)注點例子。方面用Spring的 Advisor或攔截器實現(xiàn)。

  • 連接點(Joinpoint) : 程序執(zhí)行過程中明確的點,如方法的調(diào) 用或特定的異常被拋出。

  • 通知(Advice) : 在特定的連接點,AOP框架執(zhí)行的動作。各種類 型的通知包括“around”、“before”和“throws”通知。通知類型將在下面討論。許多AOP框架 包括Spring都是以 攔截器 做通知模型,維護一個“圍繞”連接點的攔截器 鏈。

  • 切入點(Pointcut) : 指定一個通知將被引發(fā)的一系列連接點 的集合。AOP框架必須允許開發(fā)者指定切入點:例如,使用正則表達式。

  • 引入(Introduction) : 添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現(xiàn) IsModified 接口,來簡化緩存。

  • 目標對象(Target Object) : 包含連接點的對象。也被稱作 被通知 被代理 對象。

  • AOP代理(AOP Proxy) : AOP框架創(chuàng)建的對象,包含通知。 在Spring中,AOP代理可以是JDK動態(tài)代理或者CGLIB代理。

  • 織入(Weaving) : 組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時 完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純Java AOP框架一樣, 在運行時完成織入。

各種通知類型包括:

  • Around通知 : 包圍一個連接點的通知,如方法調(diào)用。這是最 強大的通知。Aroud通知在方法調(diào)用前后完成自定義的行為。它們負責(zé)選擇繼續(xù)執(zhí)行連接點或通過 返回它們自己的返回值或拋出異常來短路執(zhí)行。

  • Before通知 : 在一個連接點之前執(zhí)行的通知,但這個通知 不能阻止連接點前的執(zhí)行(除非它拋出一個異常)。

  • Throws通知 : 在方法拋出異常時執(zhí)行的通知。Spring提供 強類型的Throws通知,因此你可以書寫代碼捕獲感興趣的異常(和它的子類),不需要從Throwable 或Exception強制類型轉(zhuǎn)換。

  • After returning通知 : 在連接點正常完成后執(zhí)行的通知, 例如,一個方法正常返回,沒有拋出異常。

Around通知是最通用的通知類型。大部分基于攔截的AOP框架,如Nanning和JBoss4,只提供 Around通知。

如同AspectJ,Spring提供所有類型的通知,我們推薦你使用最為合適的通知類型來實現(xiàn)需 要的行為。例如,如果只是需要用一個方法的返回值來更新緩存,你最好實現(xiàn)一個after returning 通知而不是around通知,雖然around通知也能完成同樣的事情。使用最合適的通知類型使編程模型變 得簡單,并能減少潛在錯誤。例如你不需要調(diào)用在around通知中所需使用的的MethodInvocation的 proceed() 方法,因此就調(diào)用失敗。

切入點的概念是AOP的關(guān)鍵,使AOP區(qū)別于其它使用攔截的技術(shù)。切入點使通知獨立于OO的 層次選定目標。例如,提供聲明式事務(wù)管理的around通知可以被應(yīng)用到跨越多個對象的一組方法上。 因此切入點構(gòu)成了AOP的結(jié)構(gòu)要素。

5.1.2.?Spring AOP的功能

Spring AOP用純Java實現(xiàn)。它不需要特別的編譯過程。Spring AOP不需要控制類裝載器層次, 因此適用于J2EE web容器或應(yīng)用服務(wù)器。

Spring目前支持攔截方法調(diào)用。成員變量攔截器沒有實現(xiàn),雖然加入成員變量攔截器支持并不破壞 Spring AOP核心API。

成員變量攔截器在違反OO封裝原則方面存在爭論。我們不認為這在應(yīng)用程序開發(fā)中是明智的。如 果你需要使用成員變量攔截器,考慮使用AspectJ。

Spring提供代表切入點或各種通知類型的類。Spring使用術(shù)語 advisor 來 表示代表方面的對象,它包含一個通知和一個指定特定連接點的切入點。

各種通知類型有 MethodInterceptor (來自AOP聯(lián)盟的攔截器API)和定義在 org.springframework.aop 包中的 通知接口。所有通知必須實現(xiàn) org.aopalliance.aop.Advice 標簽接口。 取出就可使用的通知有 MethodInterceptor 、 ThrowsAdvice 、 BeforeAdvice AfterReturningAdvice 。我們將在下面詳細討論這些通知類型。

Spring實現(xiàn)了 AOP聯(lián)盟 的攔截器接口( http://www.sourceforge.net/projects/aopalliance ). Around通知必須實現(xiàn)AOP聯(lián)盟的 org.aopalliance.intercept.MethodInterceptor 接口。這個接口的實現(xiàn)可以運行在Spring或其他AOP聯(lián)盟兼容的實現(xiàn)中。目前JAC實現(xiàn)了AOP聯(lián)盟的接 口,Nanning和Dynaop可能在2004年早期實現(xiàn)。

Spring實現(xiàn)AOP的途徑不同于其他大部分AOP框架。它的目標不是提供及其完善的AOP實現(xiàn)( 雖然Spring AOP非常強大);而是提供一個和Spring IoC緊密整合的AOP實現(xiàn),幫助解決企業(yè)應(yīng)用 中的常見問題。 因此,例如Spring AOP的功能通常是和Spring IoC容器聯(lián)合使用的。AOP通知是用普通 的bean定義語法來定義的(雖然可以使用"autoproxying"功能);通知和切入點本身由Spring IoC 管理:這是一個重要的其他AOP實現(xiàn)的區(qū)別。有些事使用Spring AOP是無法容易或高效地實現(xiàn),比如通知 非常細粒度的對象。這種情況AspectJ可能是最合適的選擇。但是,我們的經(jīng)驗是Spring針對J2EE應(yīng) 用中大部分能用AOP解決的問題提供了一個優(yōu)秀的解決方案。

5.1.3.?Spring中AOP代理

Spring默認使用JDK 動態(tài)代理 實現(xiàn)AOP代理。這使得任何接口或 接口的集合能夠被代理。

Spring也可以是CGLIB代理。這可以代理類,而不是接口。如果業(yè)務(wù)對象沒有實現(xiàn)一個接口, CGLIB被默認使用。但是作為一 針對接口編程而不是類編程 良好實踐,業(yè)務(wù)對象 通常實現(xiàn)一個或多個業(yè)務(wù)接口。

也可以強制使用CGLIB:我們將在下面討論,并且會解釋為什么你會要這么做。

Spring 1.0后,Spring可能提供額外的AOP代理的類型,包括完全生成的類。這將不會影響 編程模型。

5.2.?Spring的切入點

讓我們看看Spring如何處理切入點這個重要的概念。

5.2.1.?概念

Spring的切入點模型能夠使切入點獨立于通知類型被重用。 同樣的切入點有可能接受不同的 通知。

org.springframework.aop.Pointcut 接口是重要的接口, 用來指定通知到特定的類和方法目標。完整的接口定義如下:

          public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}
        

Pointcut 接口分成兩個部分有利于重用類和方法的匹配部分,并且組合細粒度的 操作(如和另一個方法匹配器執(zhí)行一個”并“的操作)。

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代理創(chuàng)建的時候執(zhí)行,避免在所有方法調(diào)用時都需要進行 測試。如果2個參數(shù)的匹配方法對某個方法返回true,并且MethodMatcher的 isRuntime() 也返回true,那么3個參數(shù)的匹配方法將在每次方法調(diào)用的時候被調(diào)用。這使 切入點能夠在目標通知被執(zhí)行之前立即查看傳遞給方法調(diào)用的參數(shù)。

大部分MethodMatcher都是靜態(tài)的,意味著 isRuntime() 方法 返回false。這種情況下3個參數(shù)的匹配方法永遠不會被調(diào)用。

如果可能,盡量使切入點是靜態(tài)的,使當(dāng)AOP代理被創(chuàng)建時,AOP框架能夠緩存切入點的 測試結(jié)果。

5.2.2.?切入點的運算

Spring支持的切入點的運算有: 值得注意的是 。

并表示只要任何一個切入點匹配的方法。

交表示兩個切入點都要匹配的方法。

并通常比較有用。

切入點可以用 org.springframework.aop.support.Pointcuts 類的靜態(tài)方法來組合,或者使用同一個包中的 ComposablePointcut 類。

5.2.3.?實用切入點實現(xiàn)

Spring提供幾個實用的切入點實現(xiàn)。一些可以直接使用。另一些需要子類化來實現(xiàn)應(yīng)用相 關(guān)的切入點。

5.2.3.1.?靜態(tài)切入點

靜態(tài)切入點只基于方法和目標類,而不考慮方法的參數(shù)。靜態(tài)切入點足夠滿足大多數(shù)情況 的使用。Spring可以只在方法第一次被調(diào)用的時候計算靜態(tài)切入點,不需要在每次方法調(diào)用 的時候計算。

讓我們看一下Spring提供的一些靜態(tài)切入點的實現(xiàn)。

5.2.3.1.1.?正則表達式切入點

一個很顯然的指定靜態(tài)切入點的方法是正則表達式。除了Spring以外,其它的AOP框架也實 現(xiàn)了這一點。 org.springframework.aop.support.RegexpMethodPointcut 是一個通用的正則表達式切入點,它使用Perl 5的正則表達式的語法。

使用這個類你可以定義一個模式的列表。如果任何一個匹配,那個切入點將被計算成 true。(所以結(jié)果相當(dāng)于是這些切入點的并集)。

用法如下:

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

RegexpMethodPointcut 一個實用子類, RegexpMethodPointcutAdvisor , 允許我們同時引用一個通知。 (記住通知可以是攔截器,before通知,throws通知等等。)這簡化了bean的裝配,因為一個bean 可以同時當(dāng)作切入點和通知,如下所示:

              <bean id="settersAndAbsquatulateAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="interceptor">
        <ref local="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*get.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>
            

RegexpMethodPointcutAdvisor 可以用于任何通知類型。

RegexpMethodPointcut類需要Jakarta ORO正則表達式包。
5.2.3.1.2.?屬性驅(qū)動的切入點

一類重要的靜態(tài)切入點是 元數(shù)據(jù)驅(qū)動 的 切入點。 它使用元數(shù)據(jù)屬性的值:典型地,使用源代碼級元數(shù)據(jù)。

5.2.3.2.?動態(tài)切入點

動態(tài)切入點的演算代價比靜態(tài)切入點高的多。它們不僅考慮靜態(tài)信息,還要考慮方法的 參數(shù) 。這意味著它們必須在每次方法調(diào)用的時候都被計算;并且不能緩存結(jié)果 ,因為參數(shù)是變化的。

這個主要的例子就是 控制流 切入點。

5.2.3.2.1.?控制流切入點

Spring的控制流切入點概念上和AspectJ的 cflow 切入點一致,雖然沒有其那么強大(當(dāng)前沒有辦法指定一個切入點在另一個切入點后執(zhí)行)。 一個控制流切入點匹配當(dāng)前的調(diào)用棧。例如,連接點被 com.mycompany.web 包或者 SomeCaller 類中一個方法調(diào)用的時候,觸發(fā)該切入點??刂屏髑腥朦c的實現(xiàn)類是 org.springframework.aop.support.ControlFlowPointcut 。

注意

控制流切入點是動態(tài)切入點中計算代價最高的。Java 1.4中, 它的運行開銷是其他動態(tài)切入點的5倍。在Java 1.3中則超過10倍。

5.2.4.?切入點超類

Spring提供非常實用的切入點的超類幫助你實現(xiàn)你自己的切入點。

因為靜態(tài)切入點非常實用,你很可能子類化StaticMethodMatcherPointcut,如下所示。 這只需要實現(xiàn)一個抽象方法(雖然可以改寫其它的方法來自定義行為)。

          class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

當(dāng)然也有動態(tài)切入點的超類。

Spring 1.0 RC2或以上版本,自定義切入點可以用于任何類型的通知。

5.2.5.?自定義切入點

因為Spring中的切入點是Java類,而不是語言特性(如AspectJ),因此可以定義自定義切入點, 無論靜態(tài)還是動態(tài)。但是,沒有直接支持用AspectJ語法書寫的復(fù)雜的切入點表達式。不過, Spring的自定義切入點也可以任意的復(fù)雜。

后續(xù)版本的Spring可能象JA一樣C提供”語義切入點“的支持:例如,“所有更改目標對象 實例變量的方法”。

5.3.?Spring的通知類型

現(xiàn)在讓我們看看Spring AOP是如何處理通知的。

5.3.1.?通知的生命周期

Spring的通知可以跨越多個被通知對象共享,或者每個被通知對象有自己的通知。這分別對應(yīng) per-class per-instance 通知。

Per-class通知使用最為廣泛。它適合于通用的通知,如事務(wù)adisor。它們不依賴被代理 的對象的狀態(tài),也不添加新的狀態(tài)。它們僅僅作用于方法和方法的參數(shù)。

Per-instance通知適合于導(dǎo)入,來支持混入(mixin)。在這種情況下,通知添加狀態(tài)到 被代理的對象。

可以在同一個AOP代理中混合使用共享和per-instance通知。

5.3.2.?Spring中通知類型

Spring提供幾種現(xiàn)成的通知類型并可擴展提供任意的通知類型。讓我們看看基本概念和 標準的通知類型。

5.3.2.1.?Interception around advice

Spring中最基本的通知類型是 interception around advice .

Spring使用方法攔截器的around通知是和AOP聯(lián)盟接口兼容的。實現(xiàn)around通知的 類需要實現(xiàn)接口MethodInterceptor:

            public interface MethodInterceptor extends Interceptor {
  
    Object invoke(MethodInvocation invocation) throws Throwable;
}
          

invoke() 方法的 MethodInvocation 參數(shù)暴露將被調(diào)用的方法、目標連接點、AOP代理和傳遞給被調(diào)用方法的參數(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)用。 這個調(diào)用會應(yīng)用到目標連接點的攔截器鏈中的每一個攔截器。大部分攔截器會調(diào)用這個方法,并返回它的返回值。但是, 一個MethodInterceptor,和任何around通知一樣,可以返回不同的值或者拋出一個異常,而 不調(diào)用proceed方法。但是,沒有好的原因你要這么做。

MethodInterceptor提供了和其他AOP聯(lián)盟的兼容實現(xiàn)的交互能力。這一節(jié)下面 要討論的其他的通知類型實現(xiàn)了AOP公共的概念,但是以Spring特定的方式。雖然使用特定 通知類型有很多優(yōu)點,但如果你可能需要在其他的AOP框架中使用,請堅持使用MethodInterceptor around通知類型。注意目前切入點不能和其它框架交互操作,并且AOP聯(lián)盟目前也沒有定義切入 點接口。

5.3.2.2.?Before通知

Before通知 是一種簡單的通知類型。 這個通知不需要一個 MethodInvocation 對象,因為它只在進入一個方法 前被調(diào)用。

Before通知的主要優(yōu)點是它不需要調(diào)用 proceed() 方法, 因此沒有無意中忘掉繼續(xù)執(zhí)行攔截器鏈的可能性。

MethodBeforeAdvice 接口如下所示。 (Spring的API設(shè)計允許成員變量的before通知,雖然一般的對象都可以應(yīng)用成員變量攔截,但Spring 有可能永遠不會實現(xiàn)它)。

            public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}
          

注意返回類型是 void 。 Before通知可以在連接點執(zhí)行之前 插入自定義的行為,但是不能改變返回值。如果一個before通知拋出一個異常,這將中斷攔截器 鏈的進一步執(zhí)行。這個異常將沿著攔截器鏈后退著向上傳播。如果這個異常是unchecked的,或者 出現(xiàn)在被調(diào)用的方法的簽名中,它將會被直接傳遞給客戶代碼;否則,它將被AOP代理包裝到一個unchecked 的異常里。

下面是Spring中一個before通知的例子,這個例子計數(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; 
    }
}
          
Before通知可以被用于任何類型的切入點。

5.3.2.3.?Throws通知

如果連接點拋出異常, Throws通知 在連接點返回后被調(diào)用。Spring提供強類型的throws通知。注意這意味著 org.springframework.aop.ThrowsAdvice 接口不包含任何方法: 它是一個標記接口,標識給定的對象實現(xiàn)了一個或多個強類型的throws通知方法。這些方法形式 如下:

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

只有最后一個參數(shù)是必需的。 這樣從一個參數(shù)到四個參數(shù),依賴于通知是否對方法和方法 的參數(shù)感興趣。下面是throws通知的例子。

如果拋出 RemoteException 異常(包括子類), 這個通知會被調(diào)用

            public  class RemoteThrowsAdvice implements ThrowsAdvice {

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

如果拋出 ServletException 異常, 下面的通知會被調(diào)用。和上面的通知不一樣,它聲明了四個參數(shù),所以它可以訪問被調(diào)用的方法,方法的參數(shù) 和目標對象:

            public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最后一個例子演示了如何在一個類中使用兩個方法來同時處理 RemoteException ServletException 異常。任意個數(shù)的throws方法可以被組合在一個類中。

            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
    }
}
          
Throws通知可被用于任何類型的切入點。

5.3.2.4.?After Returning通知

Spring中的after returning通知必須實現(xiàn) org.springframework.aop.AfterReturningAdvice 接口,如下所示:

            public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target) 
            throws Throwable;
}
          

After returning通知可以訪問返回值(不能改變)、被調(diào)用的方法、方法的參數(shù)和 目標對象。

下面的after returning通知統(tǒng)計所有成功的沒有拋出異常的方法調(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í)行路徑。如果它拋出一個異常,這個異常而不是返回值將被沿著攔截器鏈 向上拋出。

After returning通知可被用于任何類型的切入點。

5.3.2.5.?Introduction通知

Spring將introduction通知看作一種特殊類型的攔截通知。

Introduction需要實現(xiàn) IntroductionAdvisor , 和 IntroductionInterceptor 接口:

            public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
          

繼承自AOP聯(lián)盟 MethodInterceptor 接口的 invoke() 方法必須實現(xiàn)導(dǎo)入:也就是說,如果被調(diào)用的方法是在 導(dǎo)入的接口中,導(dǎo)入攔截器負責(zé)處理這個方法調(diào)用,它不能調(diào)用 proceed() 方法。

Introduction通知不能被用于任何切入點,因為它只能作用于類層次上,而不是方法。 你可以只用InterceptionIntroductionAdvisor來實現(xiàn)導(dǎo)入通知,它有下面的方法:

            public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

    ClassFilter getClassFilter();

    IntroductionInterceptor getIntroductionInterceptor();

    Class[] getInterfaces();
}
          

這里沒有 MethodMatcher ,因此也沒有和導(dǎo)入通知關(guān)聯(lián)的 切入點 。只有類過濾是合乎邏輯的。

getInterfaces() 方法返回advisor導(dǎo)入的接口。

讓我們看看一個來自Spring測試套件中的簡單例子。我們假設(shè)想要導(dǎo)入下面的接口到一個 或者多個對象中:

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

這個例子演示了一個 mixin 。我們想要能夠 將被通知對象類型轉(zhuǎn)換為Lockable,不管它們的類型,并且調(diào)用lock和unlock方法。如果我們調(diào)用 lock()方法,我們希望所有setter方法拋出 LockedException 異常。 這樣我們能添加一個方面使的對象不可變,而它們不需要知道這一點:這是一個很好的AOP例 子。

首先,我們需要一個做大量轉(zhuǎn)化的 IntroductionInterceptor 。 在這里,我們繼承 org.springframework.aop.support.DelegatingIntroductionInterceptor 實用類。我們可以直接實現(xiàn)IntroductionInterceptor接口,但是大多數(shù)情況下 DelegatingIntroductionInterceptor 是最合適的。

DelegatingIntroductionInterceptor 的設(shè)計是將導(dǎo)入 委托到真正實現(xiàn)導(dǎo)入接口的接口,隱藏完成這些工作的攔截器。委托可以使用構(gòu)造方法參數(shù) 設(shè)置到任何對象中;默認的委托就是自己(當(dāng)無參數(shù)的構(gòu)造方法被使用時)。這樣在下面的 例子里,委托是 DelegatingIntroductionInterceptor 的子類 LockMixin 。給定一個委托(默認是自身)的 DelegatingIntroductionInterceptor 實例尋找被這個委托(而不 是IntroductionInterceptor)實現(xiàn)的所有接口,并支持它們中任何一個導(dǎo)入。子類如 LockMixin 也可能調(diào)用 suppressInterflace(Class intf) 方法隱藏不應(yīng)暴露的接口。然而,不管 IntroductionInterceptor 準備支持多少接口, IntroductionAdvisor 將控制哪個接口將被實際 暴露。一個導(dǎo)入的接口將隱藏目標的同一個接口的所有實現(xiàn)。

這樣,LockMixin繼承 DelegatingIntroductionInterceptor 并自己實現(xiàn)Lockable。父類自動選擇支持導(dǎo)入的Lockable,所以我們不需要指定它。 用這種方法我們可以導(dǎo)入任意數(shù)量的接口。

注意 locked 實例變量的使用。這有效地添加額外的狀態(tài)到目標 對象。

            public class LockMixin extends DelegatingIntroductionInterceptor 
    implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
            throw new LockedException();
        return super.invoke(invocation);
    }

}
          

通常不要需要改寫 invoke() 方法:實現(xiàn) DelegatingIntroductionInterceptor 就足夠了,如果是導(dǎo)入的方法, DelegatingIntroductionInterceptor 實現(xiàn)會調(diào)用委托方法, 否則繼續(xù)沿著連接點處理。在現(xiàn)在的情況下,我們需要添加一個檢查:在上鎖 狀態(tài)下不能調(diào)用setter方法。

所需的導(dǎo)入advisor是很簡單的。只有保存一個獨立的 LockMixin 實例,并指定導(dǎo)入的接口,在這里就是 Lockable 。一個稍微復(fù)雜一點例子可能需要一個導(dǎo)入攔截器(可以 定義成prototype)的引用:在這種情況下, LockMixin 沒有相關(guān)配置,所以我們簡單地 使用 new 來創(chuàng)建它。

            public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我們可以非常簡單地使用這個advisor:它不需要任何配置。(但是,有一點 必要的:就是不可能在沒有 IntroductionAdvisor 的情況下使用 IntroductionInterceptor 。) 和導(dǎo)入一樣,通常 advisor必須是針對每個實例的,并且是有狀態(tài)的。我們會有不同的的 LockMixinAdvisor 每個被通知對象,會有不同的 LockMixin 。 advisor組成了被通知對象的狀態(tài)的一部分。

和其他advisor一樣,我們可以使用 Advised.addAdvisor() 方法以編程地方式使用這種advisor,或者在XML中配置(推薦這種方式)。 下面將討論所有代理創(chuàng)建,包括“自動代理創(chuàng)建者”,選擇代理創(chuàng)建以正確地處理導(dǎo)入和有狀態(tài)的混入。

5.4.?Spring中的advisor

在Spring中,一個advisor就是一個aspect的完整的模塊化表示。 一般地,一個advisor包括通知和切入點。

撇開導(dǎo)入這種特殊情況,任何advisor可被用于任何通知。 org.springframework.aop.support.DefaultPointcutAdvisor 是最通用的advisor類。例如,它可以和 MethodInterceptor 、 BeforeAdvice 或者 ThrowsAdvice 一起使 用。

Spring中可以將advisor和通知混合在一個AOP代理中。例如,你可以在一個代理配置中 使用一個對around通知、throws通知和before通知的攔截:Spring將自動創(chuàng)建必要的攔截器鏈。

5.5.?用ProxyFactoryBean創(chuàng)建AOP代理

如果你在為你的業(yè)務(wù)對象使用Spring的IoC容器(例如ApplicationContext或者BeanFactory), 你應(yīng)該會或者你愿意會使用Spring的aop FactoryBean(記住,factory bean引入了一個間接層, 它能創(chuàng)建不同類型的對象).

在spring中創(chuàng)建AOP proxy的基本途徑是使用org.springframework.aop.framework.ProxyFactoryBean. 這樣可以對pointcut和advice作精確控制。但是如果你不需要這種控制,那些簡單的選擇可能更適合你。

5.5.1.?基本概要

ProxyFactoryBean ,和其他Spring的 FactoryBean 實現(xiàn)一樣,引入一個間接的層次。如果你 定義一個名字為 foo ProxyFactoryBean , 引用 foo 的對象所看到的不是 ProxyFactoryBean 實例本身,而是由實現(xiàn) ProxyFactoryBean 的類的 getObject() 方法所創(chuàng)建的對象。這個方法將創(chuàng)建一個包裝了目標對象 的AOP代理。

使用 ProxyFactoryBean 或者其他IoC可知的類來創(chuàng)建AOP代理 的最重要的優(yōu)點之一是IoC可以管理通知和切入點。這是一個非常的強大的功能,能夠?qū)?現(xiàn)其他AOP框架很難實現(xiàn)的特定的方法。例如,一個通知本身可以引用應(yīng)用對象(除了目標對象, 它在任何AOP框架中都可以引用應(yīng)用對象),這完全得益于依賴注入所提供的可插入性。

5.5.2.?JavaBean的屬性

類似于Spring提供的絕大部分FactoryBean實現(xiàn)一樣, ProxyFactoryBean 也是一個javabean,我們可以利用它的屬性來:

  • 指定你將要代理的目標

  • 指定是否使用CGLIB

一些關(guān)鍵屬性來自 org.springframework.aop.framework.ProxyConfig :它是所有AOP代理工廠的父類。這些關(guān)鍵屬性包括:

  • proxyTargetClass : 如果我們應(yīng)該代理目標類, 而不是接口,這個屬性的值為true。如果這是true,我們需要使用CGLIB。

  • optimize : 是否使用強優(yōu)化來創(chuàng)建代理。不要使用 這個設(shè)置,除非你了解相關(guān)的AOP代理是如何處理優(yōu)化的。目前這只對CGLIB代理有效;對JDK 動態(tài)代理無效(默認)。

  • frozen : 是否禁止通知的改變,一旦代理工廠已經(jīng)配置。 默認是false。

  • exposeProxy : 當(dāng)前代理是否要暴露在ThreadLocal中, 以便它可以被目標對象訪問。(它可以通過MethodInvocation得到,不需要ThreadLocal)。 如果一個目標需要獲得它的代理并且exposeProxy的值是ture,可以使用 AopContext.currentProxy() 方法。

  • aopProxyFactory : 所使用的AopProxyFactory具體實現(xiàn)。 這個參數(shù)提供了一條途徑來定義是否使用動態(tài)代理、CGLIB還是其他代理策略。默認實現(xiàn)將適當(dāng)?shù)剡x擇動態(tài) 代理或CGLIB。一般不需要使用這個屬性;它的意圖是允許Spring 1.1使用另外新的代理類型。

其他 ProxyFactoryBean 特定的屬性包括:

  • proxyInterfaces : 接口名稱的字符串?dāng)?shù)組。如果這個 沒有提供,CGLIB代理將被用于目標類。

  • interceptorNames : Advisor、interceptor或其他 被應(yīng)用的通知名稱的字符串?dāng)?shù)組。順序是很重要的。這里的名稱是當(dāng)前工廠中bean的名稱,包 括來自祖先工廠的bean的名稱。

  • singleton : 工廠是否返回一個單獨的對象,無論 getObject() 被調(diào)用多少次。許多 FactoryBean 的實現(xiàn)提供這個方法。默認值是true。如果你想要使用有狀態(tài)的通知--例如,用于有狀態(tài)的 mixin--將這個值設(shè)為false,使用prototype通知。

5.5.3.?代理接口

讓我們來看一個簡單的ProxyFactoryBean的實際例子。這個例子涉及到 :

  • 一個將被代理的目標bean,在這個例子里,這個bean的被定義為"personTarget".

  • 一個advisor和一個interceptor來提供advice.

  • 一個AOP代理bean定義,該bean指定目標對象(這里是personTarget bean), 代理接口,和使用的advice.

          <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.NopInterceptor">
</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>
        

請注意:person bean的 interceptorNames 屬性提供一個String列表, 列出的是該ProxyFactoryBean使用的,在當(dāng)前bean工廠定義的interceptor或者advisor的 名字(advisor,interceptor,before,after returning,和throws advice 對象皆可)。 Advisor在該列表中的次序很重要。

你也許會對該列表為什么不采用bean的引用存有疑問。 原因就在于如果ProxyFactoryBean的singleton屬性被設(shè)置為false, 那么bean工廠必須能返回多個獨立的代理實例。 如果有任何一個advisor本身是prototype的,那么它就需要返回獨立的實例, 也就是有必要從bean工廠獲取advisor的不同實例,bean的引用在這里顯然是不夠的。

上面定義的“person”bean定義可以作為Person接口的實現(xiàn)來使用,如下所示:

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

在同一個IoC的上下文中,其他的bean可以依賴于Person接口,就象依賴于一個普通的java對象一樣。

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

在這個例子里, PersonUser 類暴露了一個類型為Person的屬性。 只要是在用到該屬性的地方,AOP代理都能透明的替代一個真實的Person實現(xiàn)。 但是,這個類可能是一個動態(tài)代理類。也就是有可能把它類型轉(zhuǎn)換為一個 Advised 接口 (該接口在下面的章節(jié)中論述) 。

5.5.4.?代理類

如果你需要代理的是類,而不是一個或多個接口,又該怎么辦呢?

想象一下我們上面的例子,如果沒有 Person 接口, 我們需要通知一個叫 Person 的類, 而且該類沒有實現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置Spring使用CGLIB代理, 而不是動態(tài)代理。你只要在上面的 ProxyFactoryBean 定義中把 它的 proxyTargetClass 屬性改成true就行了。

只要你愿意,即使在有接口的情況下,你也可以強迫Spring使用CGLIB代理。

CGLIB代理是通過在運行期產(chǎn)生目標類的子類來進行工作的。 Spring可以配置這個生成的子類,來代理原始目標類的方法調(diào)用。這個子類是用 Decorator 設(shè)計模式置入到advice中的。

CGLIB代理對于用戶來說應(yīng)該是透明的。然而,還有以下一些因素需要考慮:

  • Final 方法不能被通知,因為不能被重寫。

  • 你需要在你的classpath中包括CGLIB的二進制代碼,而動態(tài)代理對任何JDK都是可用的.

CGLIB和動態(tài)代理在性能上有微小的區(qū)別,對Spring 1.0來說,后者稍快。 另外,以后可能會有變化。在這種情況下性能不是決定性因素

5.6.?便利的代理創(chuàng)建方式

通常,我們不需要 ProxyFactoryBean 的全部功能,因為我們常常只對一個方面感興趣: 例如,事務(wù)管理。

當(dāng)我們僅僅對一個特定的方面干興趣時,我們可以使用許多便利的工廠來創(chuàng)建AOP代理。這些在其他 章節(jié)討論,所以這里我們快速瀏覽一下它們。

5.6.1.?TransactionProxyFactoryBean

用Spring提供的 jPetStore 的示例應(yīng)用 演示了TransactionProxyFactoryBean的使用方式。

TransactionProxyFactoryBean ProxyConfig 的子類, 因此基本配置信息是和 ProxyFactoryBean 共享的。 (見上面 ProxyConfig 的屬性列表。)

下面的代碼來自于JPetStore application,演示了 ProxyFactoryBean 是如何工作的。 跟ProxyFactoryBean一樣,存在一個目標bean的定義。 類的依賴關(guān)系定義在代理工廠bean定義中(petStore),而不是普通Java對象(petStoreTarget)。

TransactionProxyFactoryBean 需要設(shè)置一個 target 屬性, 還需要設(shè)置“ transactionAttributes ”, “ transactionAttributes ”用來指定需要事務(wù)化 處理的方法,還有要求的傳播方式和其他設(shè)置:

          <bean id="petStoreTarget" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
    <property name="accountDao"><ref bean="accountDao"/></property>
    <!-- Other dependencies omitted -->
</bean>

<bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref bean="transactionManager"/></property>
    <property name="target"><ref local="petStoreTarget"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>
        

TransactionProxyFactoryBean 自動創(chuàng)建一個事務(wù)advisor, 該advisor包括一個基于事務(wù)屬性的切入點。因此只有事務(wù)性的方法被通知。

TransactionProxyFactoryBean 使用preInterceptors和 postInterceptors屬性指定“pre”和“post”通知。它們將攔截器,通知和Advisor數(shù)組放置 在事務(wù)攔截器前后的攔截器鏈中。使用XML格式的bean定義中的<list>元素定義,就 象下面一樣:

          <property name="preInterceptors">
    <list>
        <ref local="authorizationInterceptor"/>
        <ref local="notificationBeforeAdvice"/>
    </list>
</property>
<property name="postInterceptors">
    <list>
        <ref local="myAdvisor"/>
    </list>
</property>
        

這些屬性可以加到上面的“petStore”的bean定義里。一個通用用法是將事務(wù)和聲明式 安全組合在一起使用:一個和EJB提供的類似的方法。

因為使用前攔截器和后攔截器時,用的是真正的實例引用,而不象在 ProxyFactoryBean 中用的bean的名字,因此它們只能用于共享實例的通知。 因此它們不能用在有狀態(tài)的通知中:例如,在mixin中。這和TransactionProxyFactoryBean的要求是 一致的。如果你需要更復(fù)雜的,可以定制的AOP,你可以考慮使用普通的 ProxyFactoryBean , 或者是自動代理生成器(參考下面)。

尤其是如果我們將Spring的AOP在許多情況下看成是EJB的替代品,我們會發(fā)現(xiàn)大多數(shù)通知是很普通的, 可以使用共享實例。聲明式的事務(wù)管理和安全檢查是一個典型的例子。

TransactionProxyFactoryBean 依賴于由它的 transactionManager 屬性指定的 TransactionManager 。 這種事務(wù)管理方式是可插拔的,基于JTA,JDBC或者其他事務(wù)管理策略皆可。 這與Spring的事務(wù)抽象層有關(guān),而不在于AOP本身。我們將在下一章中討論事務(wù)機制。

如果你只對聲明性事務(wù)管理感興趣,TransactionProxyFactoryBean是一個不錯的解決辦法, 并且比直接使用ProxyFactoryBean來得簡單.

5.6.2.?EJB 代理

其它有一些專門的代理用于創(chuàng)建EJB代理,使得EJB的“業(yè)務(wù)方法”的接口可以被調(diào)用代碼直接使用。 調(diào)用代碼并不需要進行JNDI查找或使用EJB的創(chuàng)建方法:這是在可讀性和架構(gòu)靈活性方面的重大提高。

進一步請參考本手冊內(nèi)的Spring的EJB業(yè)務(wù)。

5.7.?使用ProxyFactory以編程的方式創(chuàng)建AOP代理

使用Spring以編程的方式創(chuàng)建AOP代理也很簡單。 這使得你不需要Spring的IoC就能夠使用Spring的AOP。

下面的代碼顯示的用攔截器和advisor為目標對象創(chuàng)建代理。目標對象實現(xiàn)的接口將自動被代理:

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

第一步是創(chuàng)建類型為 org.springframework.aop.framework.ProxyFactory 的對象。你可以和上面的例子一樣用目標對象創(chuàng)建,或者在另一個構(gòu)造函數(shù)中指定要被代理的接口。

你可以添加攔截器或advisor,在整個ProxyFactory的生命周期內(nèi)操作它們。如果你添加 IntroductionInterceptionAroundAdvisor,你可以使代理實現(xiàn)附加接口。

ProxyFactory(它是從AdvisedSupport繼承而來)也提供了一些實用方法,使你可以添加 其它通知類型,比如before通知和throws通知。AdvisedSupport是ProxyFactory和ProxyFactoryBean 的父類。

將AOP代理的創(chuàng)建和IoC框架結(jié)合起來在大多數(shù)應(yīng)用中都是最好的實現(xiàn)方式。我們推薦你和一般情況一樣, 不要將AOP配置信息放在Java代碼里。

5.8.?操作被通知對象

無論你怎么創(chuàng)建AOP代理,你都可以使用 org.springframework.aop.framework.Advised 接口來操作它們。任何AOP代理無論實現(xiàn)其它什么接口,都可以類型轉(zhuǎn)換為這個接口。這個接口包括下列方法:

        void addInterceptor(Interceptor interceptor) throws AopConfigException;

void addInterceptor(int pos, Interceptor interceptor) 
        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,使用當(dāng)前索引返回的advisor就是你添加的對象。如果你添加攔截器或其它通知類型, Spring將當(dāng)前對象和一個滿足要求的切入點封裝在一個advisor里。因此,如果你添加 MethodInterceptor , 使用當(dāng)前索引返回的advisor是一個 DefaultPointcutAdvisor ,這個advisor返回 MethodInterceptor 和滿足所有類和方法的切入點。

addAdvisor() 被用來添加Advisor。通常會是一個普通的 DefaultPointcutAdvisor ,它可以和任何通知或切入點(除了引用)一起使用。

缺省情況下,在每次代理被創(chuàng)建的時候添加或刪除advisor或攔截器。唯一的限制是不能添加或刪除 引入advisor,因為工廠提供的已存在的代理不反映接口的變化。(你可以從工廠得到一個新的代理來避免這個問題)

是否建議在產(chǎn)品中修改業(yè)務(wù)對象的通知還值得懷疑,雖然毫無疑問存在合理的使用情況。但是, 在開發(fā)中這是非常有用的:例如,在測試中。我有時候發(fā)現(xiàn)以攔截器或其它通知的形式來添加測試代碼非常有用, 這樣就可以進入我想要測試的方法調(diào)用。(例如,通知可以進入為這個方法創(chuàng)建的事務(wù)中: 在為回滾事務(wù)作標記前,運行SQL檢查數(shù)據(jù)庫是否被正確更新。)

根據(jù)你創(chuàng)建代理的方式,你通常可以設(shè)置 frozen 標記,這樣 Advised isFrozen() 就返回true,任何添加或刪除通知都將導(dǎo)致 AopConfigException 。這種凍結(jié)被通知對象狀態(tài)的方法在一些情況下是非常有用的: 例如,為了阻止調(diào)用代碼刪除一個安全攔截器。如果已知運行時修改通知不被允許,這還可以被Spring 1.1用來 作優(yōu)化。

5.9.?使用“autoproxy”功能

目前為止,我們已經(jīng)討論了使用 ProxyFactoryBean 或類似的工廠bean來顯式創(chuàng)建AOP代理。

Spring也允許我們使用“autoproxy”的bean定義,它可以自動代理所選擇的bean定義。這是建立在Spring的 “bean后處理器”機制上的,它能夠在容器載入bean定義的時候修改任何bean定義。

在這個模型中,你可以在你的XML bean定義文件中建立特殊的bean定義,來配置自動代理機制。這允許你聲明目標對象以使用自動代理功能: 你就可以不需要使用 ProxyFactoryBean

有兩種方法來實現(xiàn)自動代理:

  • 使用一個自動代理生成器,它引用當(dāng)前上下文中的那些特殊bean

  • 有一個特殊的自動代理創(chuàng)建的情況值得單獨考慮:由源代碼級元數(shù)據(jù)驅(qū)動的自動代理創(chuàng)建

5.9.1.?自動代理的bean定義

org.springframework.aop.framework.autoproxy 包提供了下列標準自動代理生成器。

5.9.1.1.?BeanNameAutoProxyCreator

BeanNameAutoProxyCreator為名字符合某個值或統(tǒng)配符的bean自動創(chuàng)建AOP代理。

            <bean id="jdkBeanNameProxyCreator" 
    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或任何通知類型。

就象一般的自動代理創(chuàng)建一樣,使用 BeanNameAutoProxyCreator 的主要目的 是對多個對象使用相同的配置信息,并且減少配置的工作量。這在為多個對象使用聲明式事務(wù)時是一個很流行的選擇。

在上面的例子中,名字匹配的bean定義,如“jdkMyBean”和“onlyJdk”,是包含目標類的普通bean定義。 BeanNameAutoProxyCreator 將自動創(chuàng)建AOP代理。相同的通知會被因用到所有匹配的bean。 注意,如果使用了advisor(而不是上面例子中的攔截器),切入點可能對不同的bean會不同。

5.9.1.2.?DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator 是一個更通用,更強大的自動代理生成器。它將 自動應(yīng)用于當(dāng)前上下文的符合條件的advisor,而不需要在自動代理advisor的bean定義中包含特定的bean名字。 它有助于配置的一致性,并避免象 BeanNameAutoProxyCreator 一樣重復(fù)配置。

使用這個機制包括:

  • 指定一個 DefaultAdvisorAutoProxyCreator 的bean定義

  • 在相同或相關(guān)上下文中指定任何數(shù)目的Advisor。注意這些 必須 是Advisor, 而不僅僅是攔截器或其它通知。這是很必要的,因為必須有一個切入點來檢查每個通知是否符合候選bean定義。

DefaultAdvisorAutoProxyCreator 會自動計算每個advisor包含的的切入點,看看 是否有什么通知應(yīng)該被引用到每個業(yè)務(wù)對象(比如例子中的“businessObject1”和“businessObject2”)。

這意味著任何數(shù)目的advisor都可以自動應(yīng)用到每個業(yè)務(wù)對象。如果advisor中沒有任何切入點符合業(yè)務(wù)對象的 方法,這個對象就不會被代理。因為會為新的業(yè)務(wù)對象添加bean定義,如果必要,它們會自動被代理。

一般來說,自動代理可以保證調(diào)用者或依賴無法接觸未被通知的對象。在這個ApplicationContext上 調(diào)用getBean("businessObject1")返回一個AOP代理,而不是目標業(yè)務(wù)對象。

            <bean id="autoProxyCreator"
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="txAdvisor"
    autowire="constructor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="order"><value>1</value></property>
</bean>

<bean id="customAdvisor"
    class="com.mycompany.MyAdvisor">
</bean>

<bean id="businessObject1"
    class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2"
    class="com.mycompany.BusinessObject2">
</bean>
          

如果你想在幾個業(yè)務(wù)對象上應(yīng)用相同的通知, DefaultAdvisorAutoProxyCreator 就非常有用。一旦定義恰當(dāng),你可以簡單地添加業(yè)務(wù)對象而不需要包括特定的代理配置。你也可以非常容易地 刪除所附加的方面--例如,跟蹤或性能監(jiān)控的方面--可以盡可能減少配置修改。

DefaultAdvisorAutoProxyCreator 支持過濾(使用命名規(guī)則以便只計算某一些 advisor,允許在一個工廠中使用多個,被不同配置的AdvisorAutoProxyCreator)和排序。Advisor可以實現(xiàn) org.springframework.core.Ordered 接口以保證正確的排序,如果排序確實需要。在 上面的例子中,TransactionAttributeSourceAdvisor有一個可配置的順序值,缺損是不排序。

5.9.1.3.?AbstractAdvisorAutoProxyCreator

這是DefaultAdvisorAutoProxyCreator的父類。你可以繼承它實現(xiàn)你自己的自動代理生成器,這種情況不太常見, 一般是advisor定義不能給 DefaultAdvisorAutoProxyCreator 框架的行為提供足夠的定制。

5.9.2.?使用元數(shù)據(jù)驅(qū)動的自動代理

一種特別重要的自動代理類型是由元數(shù)據(jù)驅(qū)動的。這和.NET的 ServicedComponents 編程框架 非常類似。它沒有象EJB那樣使用XML部署描述,事務(wù)管理和其它企業(yè)級業(yè)務(wù)的配置都是定義在源代碼級的屬性上。

在這種情況下,你可以使用 DefaultAdvisorAutoProxyCreator ,以及可以讀取元數(shù)據(jù)屬性的 Advisor。元數(shù)據(jù)細節(jié)定義在候選advisor的切入點部分,而不是自動代理創(chuàng)建類本身。

這是 DefaultAdvisorAutoProxyCreator 的一種特殊情況,但是它本身而言是值得考慮的。 (可以讀取元數(shù)據(jù)的代碼處于advisor的切入點中,而不是AOP框架本身。)

jPetStore示例應(yīng)用的 /attributes 目錄演示了屬性驅(qū)動的自動代理的使用。在這個例子中, 沒有必要使用 TransactionProxyFactoryBean 。僅僅在業(yè)務(wù)對象上定義業(yè)務(wù)屬性就足夠了,因為 使用了可知元數(shù)據(jù)的切入點。bean定義在 /WEB-INF/declarativeServices.xml 中,包括下 面的代碼。注意這是通用的,可以在jPetStore以外的地方使用:

          <bean id="autoproxy" 
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="transactionAttributeSource"
    class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"
    autowire="constructor">
</bean>

<bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor"
    autowire="byType">
</bean>

<bean id="transactionAdvisor"
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"
    autowire="constructor" >
</bean>

<bean id="attributes"
    class="org.springframework.metadata.commons.CommonsAttributes"
/>
        

DefaultAdvisorAutoProxyCreator bean定義--在這種情況下稱作“advisor”,但是名字 無關(guān)緊要--會在當(dāng)前的應(yīng)用上午中選擇所有符合的切入點。在這個例子中,類型為 TransactionAttributeSourceAdvisor 的“transactionAdvisor”bean定義將會應(yīng)用于包含事務(wù)屬性 的類或方法。TransactionAttributeSourceAdvisor通過構(gòu)造函數(shù)依賴于TransactionInterceptor。這個例子通過自動 裝配來解析它。 AttributesTransactionAttributeSource 依賴于 org.springframework.metadata.Attributes 接口的一個實現(xiàn)。在這段代碼中,“attributes” bean使用Jakarta Commons Attributes API來獲取屬性信息。(應(yīng)用代碼必須使用Commons Attributes編譯任務(wù)編譯。)

這里定義的 TransactionInterceptor 依賴于一個 PlatformTransactionManager 定義,它并沒有被包括在這個通用的文件中(雖然應(yīng)該是這樣), 這是因為它是和應(yīng)用的事務(wù)需求相關(guān)的(一般地,是想這個例子中的JTA,或者Hibernate,JDO 或JDBC):

          <bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>
        
如果你只要求聲明式事務(wù)管理,使用這些通用的XML定義就可以使得Spring自動代理含有事務(wù)屬性的所有類和方法。 你不需要直接和AOP打交道,并且編程模型和.NET的ServicedComponents非常相似。

這個機制具有可擴展性。它可以基于定制的屬性來使用自動代理。你需要:

  • 定義你的定制屬性。

  • 指定的Advisor包含必要的通知和由方法或類的定制屬性所觸發(fā)的切入點。你可以使用已經(jīng)存在的通知,僅僅實 現(xiàn)用來選擇定制屬性的切入點。

這些advisor可能對每個被通知類都是唯一的(例如,maxin)。它們僅僅需要被定義成 prototype bean,而不是singleton bean。例如,Spring的測試套件中的 LockMixin 引入攔截器可以和一個屬性驅(qū)動切入點一起來定位一個maxin,就象這里演示的。我們使用JavaBean配置的 普通的 DefaultPointcutAdvisor

          <bean id="lockMixin"
    class="org.springframework.aop.LockMixin"
    singleton="false"
/>

<bean id="lockableAdvisor"
    class="org.springframework.aop.support.DefaultPointcutAdvisor"
    singleton="false"
>
    <property name="pointcut">
        <ref local="myAttributeAwarePointcut"/>
    </property>
    <property name="advice">
        <ref local="lockMixin"/>
    </property>
</bean>

<bean id="anyBean" class="anyclass" ...
        

如果知道屬性的切入點符合 anyBean 或者其它bean定義中的任何方法,這個maxin 將被應(yīng)用。注意, lockMixin lockableAdvisor 定義都是 prototype的。 myAttributeAwarePointcut 切入點可以被定義成singleton,因為它不為 不同的被通知對象保存狀態(tài)。

5.10.?使用TargetSources

Spring提供了 TargetSource 的概念,由 org.springframework.aop.TargetSource 接口定義。這個接口負責(zé)返回實現(xiàn)切入點的 “目標對象”。每次AOP代理處理方法調(diào)用時,目標實例都會用到 TargetSource 實現(xiàn)。

使用Spring AOP的開發(fā)者一般不需要直接使用TargetSources,但是這提供了一種強大的方法來支持池,熱交換, 和其它復(fù)雜目標。例如,一個支持池的TargetSource可以在每次調(diào)用時返回不同的目標對象實例,使用池來管理實例。

如果你沒有指定TargetSource,就使用缺省的實現(xiàn),它封裝了一個本地對象。每次調(diào)用會返回相同的目標對象 (和你期望的一樣)。

讓我們來看一下Spring提供的標準目標源,以及如何使用它們。

當(dāng)使用定制目標源時,你的目標通常需要定義為prototype bean,而不是singleton bean。這使得 Spring在需要的時候創(chuàng)建一個新的目標實例。

5.10.1.?可熱交換的目標源

org.springframework.aop.target.HotSwappableTargetSource 允許切換一個AOP代理的目標,而調(diào)用者維持對它的引用。

修改目標源的目標會立即起作用。并且 HotSwappableTargetSource 是線程安全的。

你可以通過 HotSwappableTargetSource swap() 方法 來改變目標,就象下面一樣:

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

所需的XML定義如下:

          <bean id="initialTarget" class="mycompany.OldTarget">
</bean>

<bean id="swapper" 
    class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg><ref local="initialTarget"/></constructor-arg>
</bean>

<bean id="swappable" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource">
        <ref local="swapper"/>
    </property>
</bean>
        

上面的 swap() 調(diào)用會修改swappable這個bean的目標。持有對這個bean應(yīng)用的客戶端 將不會知道這個變化,但會立刻轉(zhuǎn)為使用新的目標對象。

雖然這個例子沒有添加任何通知--使用 TargetSource 也沒必要添加通知-- 當(dāng)然任何 TargetSource 都可以和任何一種通知一起使用。

5.10.2.?支持池的目標源

使用支持池的目標源提供了一種和無狀態(tài)的session EJB類似的編程模式,在無狀態(tài)的session EJB中,維護了 一個相同實例的池,提供從池中獲取可用對象的方法。

Spring的池和SLSB的池之間的重要區(qū)別在于Spring的池可以被應(yīng)用到任何普通Java對象。就象Spring的通用 的做法,這個業(yè)務(wù)也可以以非侵入的方式被應(yīng)用。

Spring直接支持Jakarta Commons Pool 1.1,它是一種非常高效的池實現(xiàn)。使用這個功能,你需要在你的應(yīng)用的 classpath中添加commons-pool的Jar文件。也可以直接繼承 org.springframework.aop.target.AbstractPoolingTargetSource 來支持其它池API。

下面是一個配置的例子:

          <bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" 
    singleton="false">
    ... properties omitted
</bean>

<bean id="poolTargetSource" 
    class="org.springframework.aop.target.CommonsPoolTargetSource">
    <property name="targetBeanName"><value>businessObject</value></property>
    <property name="maxSize"><value>25</value></property>
</bean>

<bean id="businessObject" 
    class="org.springframework.aop.framework.ProxyFactoryBean"
>
    <property name="targetSource"><ref local="poolTargetSource"/></property>
    <property name="interceptorNames"><value>myInterceptor</value></property>
</bean>
        

注意例子中的目標對象“businessObjectTarget” 必須 是prototype。這樣在 PoolingTargetSource 的實現(xiàn)在擴大池容量的時候可以創(chuàng)建目標的新實例。關(guān)于這些屬性的 信息可以參考 AbstractPoolingTargetSource 和子類的Javadoc。maxSize是最基本的屬性, 被保證總是存在。

在這種情況下,名字為“myInterceptor”的攔截器需要定義在同一個IoC上下文中。但是,并不一定需要 指定攔截器也用池。如果你僅需要池,并且沒有其它通知,可以根本不設(shè)置屬性interceptorNames。

也可以配置Spring以便可以將任何池化的對象轉(zhuǎn)換類型為 org.springframework.aop.target.PoolingConfig 接口。通過這個接口的一個引入,可以得到 配置信息和池的當(dāng)前大小。你需要這樣定義一個advisor:

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

通過調(diào)用 AbstractPoolingTargetSource 類上的方法,可以得到這個advisor, 因此使用MethodInvokingFactoryBean。這個advisor的名字(“poolConfigAdvisor”)必須在暴露池化對象的 This advisor is obtained by calling a convenience method on the ProxyFactoryBean中的攔截器名字列表中。

這個類型轉(zhuǎn)換就象下面:

          PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
        
池化無狀態(tài)業(yè)務(wù)對象并不是總是必要的。我們不認為這是缺省選擇,因為大多數(shù)無狀態(tài)對象自然就是線程 安全的,如果資源被緩存,實例池化會有問題。

簡單的池也可以使用自動代理。任何自動代理生成器都可以設(shè)置TargetSources。

5.10.3.?Prototype目標源

設(shè)置“prototype”目標源和支持池的目標源類似。在每次方法調(diào)用的時候都會創(chuàng)建一個新的目標實例。 雖然在現(xiàn)代JVM中創(chuàng)建對象的代價不是很高,但是裝配新對象的代價可能更高(為了maz滿足它的IoC依賴關(guān)系)。 因此沒有好的理由不應(yīng)該使用這個方法。

為了這么做,你可以修改上面的的 poolTargetSource 定義,就向下面一樣。 (為了清晰起見,我修改了名字。)

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

只有一個屬性:目標bean的名字。在TargetSource實現(xiàn)中使用繼承是為了保證命名的一致性。就象支持池的 目標源一樣,目標bean必須是一個prototype的bean定義。

5.11.?定義新的通知類型

Spring AOP設(shè)計能夠很容易地擴展。雖然攔截實現(xiàn)的策略目前只在內(nèi)部使用,但還是有可能支持攔截around通知, before通知,throws通知和after returning通知以外的任何通知類型。

org.springframework.aop.framework.adapter 包是一個支持添加新的定制通知類型而不修改核心框架的SPI(譯:可能是API)包。定制通知類型的唯一限制是它必須實現(xiàn) org.aopalliance.aop.Advice 標記接口。

更多信息請參考 org.springframework.aop.framework.adapter 包的Javadoc。

5.12.?進一步的資料和資源

對于AOP的介紹,我推薦Ramnivas Laddad (Manning, 2003)寫的 AspectJ in Action 。

進一步的Spring AOP的例子請參考Spring的示例應(yīng)用:

  • JPetStore的缺省配置演示了使用TransactionProxyFactoryBean來定義聲明式事務(wù)管理。

  • JPetStore的 /attributes 目錄演示了屬性驅(qū)動的聲明式事務(wù)管理。

如果你對Spring AOP更多高級功能感興趣,可以看一下測試套件。測試覆蓋率超過90%,并且演示了本文檔沒有提到 的許多高級功能。

5.13.?路標

Spring AOP,就象Spring的其它部分,是開發(fā)非?;钴S的部分。核心API已經(jīng)穩(wěn)定了。象Spring的其它部分一樣, AOP框架是非常模塊化的,在保留基礎(chǔ)設(shè)計的同時提供擴展。在Spring 1.1到1.2階段有很多地方可能會有所提高,但是這 些地方也保留了向后兼容性。它們是:

  • 性能的提高 :AOP代理的創(chuàng)建由工廠通過策略接口處理。因此我們能夠支持額外的AOP 代理類型而不影響用戶代碼或核心實現(xiàn)。對于Spring 1.1,我們正在檢查AOP代理實現(xiàn)的所有字節(jié)碼,萬一不需要 運行時通知改變。這應(yīng)該大大減少AOP框架的額外操作。但是注意,AOP框架的額外操作不是在普通使用中需要考慮 的內(nèi)容。

  • 更具表達力的切入點 :Spring目前提供了一個具有表達力的切入點接口,但是我們 添加更多的切入點實現(xiàn)。我們正在考慮提供一個簡單但具有強大表達式語言的實現(xiàn)。如果你希望貢獻一個有用的 切入點實現(xiàn),我們將非常歡迎。

  • 引入 方面 這個高層概念,它包含多個advisor。

Spring AOP: Spring之面向方面編程


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 天祝| 庆安县| 乌审旗| 拜泉县| 长春市| 江门市| 米易县| 马尔康县| 榕江县| 西贡区| 清远市| 闵行区| 富平县| 广南县| 鞍山市| 洛浦县| 石城县| 永靖县| 古丈县| 屏边| 鄱阳县| 梓潼县| 衡水市| 龙岩市| 息烽县| 马龙县| 宁强县| 九台市| 霍林郭勒市| 东宁县| 星子县| 靖江市| 佳木斯市| 韶关市| 叙永县| 修武县| 二连浩特市| 门头沟区| 林州市| 阿城市| 定日县|