Spring AOP: Spring之面向方面編程

5.1. 概念

面向方面編程 (AOP) 提供從另外一個角度來考慮程序結構以完善面向對象編程(OOP)。 面向對象將應用程序分解成 各個層次的對象,而AOP將程序分解成各個方面 或者說 關注點 。 這使得能夠模塊化諸如事務管理等這些橫切多個對象的關注點。(這些關注點術語稱做 橫切關注點。)java

Spring的一個關鍵組件就是AOP框架。 Spring IoC容器(BeanFactory 和ApplicationContext)並不依賴於AOP, 這意味着若是你不須要使用,AOP你能夠不用,AOP完善了Spring IoC,使之成爲一個有效的中間件解決方案,。web

AOP在Spring中的使用:正則表達式

  • 提供聲明式企業服務,特別是做爲EJB聲明式服務的替代品。這些服務中最重要的是 聲明式事務管理,這個服務創建在Spring的事務管理抽象之上。spring

  • 容許用戶實現自定義的方面,用AOP完善他們的OOP的使用。數據庫

這樣你能夠把Spring AOP看做是對Spring的補充,它使得Spring不須要EJB就能提供聲明式事務管理;或者 使用Spring AOP框架的所有功能來實現自定義的方面。編程

若是你只使用通用的聲明式服務或者預先打包的聲明式中間件服務如pooling,你能夠不直接使用 Spring AOP,而且跳過本章的大部份內容.

5.1.1. AOP概念

讓咱們從定義一些重要的AOP概念開始。這些術語不是Spring特有的。不幸的是,Spring的術語 不是特別地直觀。並且,若是Spring使用本身的術語,這將令人更加迷惑。設計模式

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

  • 鏈接點(Joinpoint): 程序執行過程當中明確的點,如方法的調 用或特定的異常被拋出。緩存

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

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

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

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

  • AOP代理(AOP Proxy): AOP框架建立的對象,包含通知。 在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。

  • 織入(Weaving): 組裝方面來建立一個被通知對象。這能夠在編譯時 完成(例如使用AspectJ編譯器),也能夠在運行時完成。Spring和其餘純Java AOP框架同樣, 在運行時完成織入。

各類通知類型包括:

  • Around通知: 包圍一個鏈接點的通知,如方法調用。這是最 強大的通知。Aroud通知在方法調用先後完成自定義的行爲。它們負責選擇繼續執行鏈接點或經過 返回它們本身的返回值或拋出異常來短路執行。

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

  • Throws通知: 在方法拋出異常時執行的通知。Spring提供 強類型的Throws通知,所以你能夠書寫代碼捕獲感興趣的異常(和它的子類),不須要從Throwable 或Exception強制類型轉換。

  • After returning通知: 在鏈接點正常完成後執行的通知, 例如,一個方法正常返回,沒有拋出異常。

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

如同AspectJ,Spring提供全部類型的通知,咱們推薦你使用最爲合適的通知類型來實現需 要的行爲。例如,若是隻是須要用一個方法的返回值來更新緩存,你最好實現一個after returning 通知而不是around通知,雖然around通知也能完成一樣的事情。使用最合適的通知類型使編程模型變 得簡單,並能減小潛在錯誤。例如你不須要調用在around通知中所需使用的的MethodInvocation的 proceed()方法,所以就調用失敗。

切入點的概念是AOP的關鍵,使AOP區別於其它使用攔截的技術。切入點使通知獨立於OO的 層次選定目標。例如,提供聲明式事務管理的around通知能夠被應用到跨越多個對象的一組方法上。 所以切入點構成了AOP的結構要素。

5.1.2. Spring AOP的功能

Spring AOP用純Java實現。它不須要特別的編譯過程。Spring AOP不須要控制類裝載器層次, 所以適用於J2EE web容器或應用服務器。

Spring目前支持攔截方法調用。成員變量攔截器沒有實現,雖然加入成員變量攔截器支持並不破壞 Spring AOP核心API。

成員變量攔截器在違反OO封裝原則方面存在爭論。咱們不認爲這在應用程序開發中是明智的。如 果你須要使用成員變量攔截器,考慮使用AspectJ。

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

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

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

Spring實現AOP的途徑不一樣於其餘大部分AOP框架。它的目標不是提供及其完善的AOP實現( 雖然Spring AOP很是強大);而是提供一個和Spring IoC緊密整合的AOP實現,幫助解決企業應用 中的常見問題。 所以,例如Spring AOP的功能一般是和Spring IoC容器聯合使用的。AOP通知是用普通 的bean定義語法來定義的(雖然可使用"autoproxying"功能);通知和切入點自己由Spring IoC 管理:這是一個重要的其餘AOP實現的區別。有些事使用Spring AOP是沒法容易或高效地實現,好比通知 很是細粒度的對象。這種狀況AspectJ多是最合適的選擇。可是,咱們的經驗是Spring針對J2EE應 用中大部分能用AOP解決的問題提供了一個優秀的解決方案。

5.1.3. Spring中AOP代理

Spring默認使用JDK動態代理實現AOP代理。這使得任何接口或 接口的集合可以被代理。

Spring也能夠是CGLIB代理。這能夠代理類,而不是接口。若是業務對象沒有實現一個接口, CGLIB被默認使用。可是做爲一針對接口編程而不是類編程良好實踐,業務對象 一般實現一個或多個業務接口。

也能夠強制使用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接口分紅兩個部分有利於重用類和方法的匹配部分,而且組合細粒度的 操做(如和另外一個方法匹配器執行一個」並「的操做)。

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代理建立的時候執行,避免在全部方法調用時都須要進行 測試。若是2個參數的匹配方法對某個方法返回true,而且MethodMatcher的 isRuntime()也返回true,那麼3個參數的匹配方法將在每次方法調用的時候被調用。這使 切入點可以在目標通知被執行以前當即查看傳遞給方法調用的參數。

大部分MethodMatcher都是靜態的,意味着isRuntime()方法 返回false。這種狀況下3個參數的匹配方法永遠不會被調用。

若是可能,儘可能使切入點是靜態的,使當AOP代理被建立時,AOP框架可以緩存切入點的 測試結果。

5.2.2. 切入點的運算

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

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

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

並一般比較有用。

切入點能夠用org.springframework.aop.support.Pointcuts 類的靜態方法來組合,或者使用同一個包中的ComposablePointcut類。

5.2.3. 實用切入點實現

Spring提供幾個實用的切入點實現。一些能夠直接使用。另外一些須要子類化來實現應用相 關的切入點。

5.2.3.1. 靜態切入點

靜態切入點只基於方法和目標類,而不考慮方法的參數。靜態切入點足夠知足大多數狀況 的使用。Spring能夠只在方法第一次被調用的時候計算靜態切入點,不須要在每次方法調用 的時候計算。

讓咱們看一下Spring提供的一些靜態切入點的實現。

5.2.3.1.1. 正則表達式切入點

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

使用這個類你能夠定義一個模式的列表。若是任何一個匹配,那個切入點將被計算成 true。(因此結果至關因而這些切入點的並集)。

用法以下:

<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 能夠同時看成切入點和通知,以下所示:

<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. 屬性驅動的切入點

一類重要的靜態切入點是元數據驅動的 切入點。 它使用元數據屬性的值:典型地,使用源代碼級元數據。

5.2.3.2. 動態切入點

動態切入點的演算代價比靜態切入點高的多。它們不只考慮靜態信息,還要考慮方法的 參數。這意味着它們必須在每次方法調用的時候都被計算;而且不能緩存結果 ,由於參數是變化的。

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

5.2.3.2.1. 控制流切入點

Spring的控制流切入點概念上和AspectJ的cflow 切入點一致,雖然沒有其那麼強大(當前沒有辦法指定一個切入點在另外一個切入點後執行)。 一個控制流切入點匹配當前的調用棧。例如,鏈接點被 com.mycompany.web包或者 SomeCaller類中一個方法調用的時候,觸發該切入點。控制流切入點的實現類是 org.springframework.aop.support.ControlFlowPointcut。

注意

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

5.2.4. 切入點超類

Spring提供很是實用的切入點的超類幫助你實現你本身的切入點。

由於靜態切入點很是實用,你極可能子類化StaticMethodMatcherPointcut,以下所示。 這隻須要實現一個抽象方法(雖然能夠改寫其它的方法來自定義行爲)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

固然也有動態切入點的超類。

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

5.2.5. 自定義切入點

由於Spring中的切入點是Java類,而不是語言特性(如AspectJ),所以能夠定義自定義切入點, 不管靜態仍是動態。可是,沒有直接支持用AspectJ語法書寫的複雜的切入點表達式。不過, Spring的自定義切入點也能夠任意的複雜。

後續版本的Spring可能象JA同樣C提供」語義切入點「的支持:例如,「全部更改目標對象 實例變量的方法」。

 

5.3. Spring的通知類型

如今讓咱們看看Spring AOP是如何處理通知的。

5.3.1. 通知的生命週期

Spring的通知能夠跨越多個被通知對象共享,或者每一個被通知對象有本身的通知。這分別對應 per-classper-instance 通知。

Per-class通知使用最爲普遍。它適合於通用的通知,如事務adisor。它們不依賴被代理 的對象的狀態,也不添加新的狀態。它們僅僅做用於方法和方法的參數。

Per-instance通知適合於導入,來支持混入(mixin)。在這種狀況下,通知添加狀態到 被代理的對象。

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

5.3.2. Spring中通知類型

Spring提供幾種現成的通知類型並可擴展提供任意的通知類型。讓咱們看看基本概念和 標準的通知類型。

5.3.2.1. Interception around advice

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

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()方法的調用。 這個調用會應用到目標鏈接點的攔截器鏈中的每個攔截器。大部分攔截器會調用這個方法,並返回它的返回值。可是, 一個MethodInterceptor,和任何around通知同樣,能夠返回不一樣的值或者拋出一個異常,而 不調用proceed方法。可是,沒有好的緣由你要這麼作。

MethodInterceptor提供了和其餘AOP聯盟的兼容實現的交互能力。這一節下面 要討論的其餘的通知類型實現了AOP公共的概念,可是以Spring特定的方式。雖然使用特定 通知類型有不少優勢,但若是你可能須要在其餘的AOP框架中使用,請堅持使用MethodInterceptor around通知類型。注意目前切入點不能和其它框架交互操做,而且AOP聯盟目前也沒有定義切入 點接口。

5.3.2.2. Before通知

Before通知是一種簡單的通知類型。 這個通知不須要一個MethodInvocation對象,由於它只在進入一個方法 前被調用。

Before通知的主要優勢是它不須要調用proceed() 方法, 所以沒有無心中忘掉繼續執行攔截器鏈的可能性。

MethodBeforeAdvice接口以下所示。 (Spring的API設計容許成員變量的before通知,雖然通常的對象均可以應用成員變量攔截,但Spring 有可能永遠不會實現它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

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

注意返回類型是void。 Before通知能夠在鏈接點執行以前 插入自定義的行爲,可是不能改變返回值。若是一個before通知拋出一個異常,這將中斷攔截器 鏈的進一步執行。這個異常將沿着攔截器鏈後退着向上傳播。若是這個異常是unchecked的,或者 出如今被調用的方法的簽名中,它將會被直接傳遞給客戶代碼;不然,它將被AOP代理包裝到一個unchecked 的異常裏。

下面是Spring中一個before通知的例子,這個例子計數全部正常返回的方法:

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通知 在鏈接點返回後被調用。Spring提供強類型的throws通知。注意這意味着 org.springframework.aop.ThrowsAdvice接口不包含任何方法: 它是一個標記接口,標識給定的對象實現了一個或多個強類型的throws通知方法。這些方法形式 以下:

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異常, 下面的通知會被調用。和上面的通知不同,它聲明瞭四個參數,因此它能夠訪問被調用的方法,方法的參數 和目標對象:

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最後一個例子演示瞭如何在一個類中使用兩個方法來同時處理 RemoteException和ServletException 異常。任意個數的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通知必須實現 org.springframework.aop.AfterReturningAdvice 接口,以下所示:

public interface AfterReturningAdvice extends Advice {

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

After returning通知能夠訪問返回值(不能改變)、被調用的方法、方法的參數和 目標對象。

下面的after returning通知統計全部成功的沒有拋出異常的方法調用:

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

這方法不改變執行路徑。若是它拋出一個異常,這個異常而不是返回值將被沿着攔截器鏈 向上拋出。

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

5.3.2.5. Introduction通知

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

Introduction須要實現IntroductionAdvisor, 和IntroductionInterceptor接口:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

繼承自AOP聯盟MethodInterceptor接口的 invoke()方法必須實現導入:也就是說,若是被調用的方法是在 導入的接口中,導入攔截器負責處理這個方法調用,它不能調用proceed() 方法。

Introduction通知不能被用於任何切入點,由於它只能做用於類層次上,而不是方法。 你能夠只用InterceptionIntroductionAdvisor來實現導入通知,它有下面的方法:

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

    ClassFilter getClassFilter();

    IntroductionInterceptor getIntroductionInterceptor();

    Class[] getInterfaces();
}

這裏沒有MethodMatcher,所以也沒有和導入通知關聯的 切入點。只有類過濾是合乎邏輯的。

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

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

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

這個例子演示了一個mixin。咱們想要可以 將被通知對象類型轉換爲Lockable,無論它們的類型,而且調用lock和unlock方法。若是咱們調用 lock()方法,咱們但願全部setter方法拋出LockedException異常。 這樣咱們能添加一個方面使的對象不可變,而它們不須要知道這一點:這是一個很好的AOP例 子。

首先,咱們須要一個作大量轉化的IntroductionInterceptor。 在這裏,咱們繼承 org.springframework.aop.support.DelegatingIntroductionInterceptor 實用類。咱們能夠直接實現IntroductionInterceptor接口,可是大多數狀況下 DelegatingIntroductionInterceptor是最合適的。

DelegatingIntroductionInterceptor的設計是將導入 委託到真正實現導入接口的接口,隱藏完成這些工做的攔截器。委託可使用構造方法參數 設置到任何對象中;默認的委託就是本身(當無參數的構造方法被使用時)。這樣在下面的 例子裏,委託是DelegatingIntroductionInterceptor的子類 LockMixin。給定一個委託(默認是自身)的 DelegatingIntroductionInterceptor實例尋找被這個委託(而不 是IntroductionInterceptor)實現的全部接口,並支持它們中任何一個導入。子類如 LockMixin也可能調用suppressInterflace(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就足夠了,若是是導入的方法, DelegatingIntroductionInterceptor實現會調用委託方法, 不然繼續沿着鏈接點處理。在如今的狀況下,咱們須要添加一個檢查:在上鎖 狀態下不能調用setter方法。

所需的導入advisor是很簡單的。只有保存一個獨立的 LockMixin實例,並指定導入的接口,在這裏就是 Lockable。一個稍微複雜一點例子可能須要一個導入攔截器(能夠 定義成prototype)的引用:在這種狀況下,LockMixin沒有相關配置,因此咱們簡單地 使用new來建立它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

咱們能夠很是簡單地使用這個advisor:它不須要任何配置。(可是,有一點 必要的:就是不可能在沒有IntroductionAdvisor 的狀況下使用IntroductionInterceptor。) 和導入同樣,一般 advisor必須是針對每一個實例的,而且是有狀態的。咱們會有不一樣的的LockMixinAdvisor 每一個被通知對象,會有不一樣的LockMixin。 advisor組成了被通知對象的狀態的一部分。

和其餘advisor同樣,咱們可使用 Advised.addAdvisor() 方法以編程地方式使用這種advisor,或者在XML中配置(推薦這種方式)。 下面將討論全部代理建立,包括「自動代理建立者」,選擇代理建立以正確地處理導入和有狀態的混入。

 

5.4. Spring中的advisor

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

撇開導入這種特殊狀況,任何advisor可被用於任何通知。 org.springframework.aop.support.DefaultPointcutAdvisor 是最通用的advisor類。例如,它能夠和MethodInterceptor、 BeforeAdvice或者ThrowsAdvice一塊兒使 用。

Spring中能夠將advisor和通知混合在一個AOP代理中。例如,你能夠在一個代理配置中 使用一個對around通知、throws通知和before通知的攔截:Spring將自動建立必要的攔截器鏈。

 

5.5. 用ProxyFactoryBean建立AOP代理

若是你在爲你的業務對象使用Spring的IoC容器(例如ApplicationContext或者BeanFactory), 你應該會或者你願意會使用Spring的aop FactoryBean(記住,factory bean引入了一個間接層, 它能建立不一樣類型的對象).

在spring中建立AOP proxy的基本途徑是使用org.springframework.aop.framework.ProxyFactoryBean. 這樣能夠對pointcut和advice做精確控制。可是若是你不須要這種控制,那些簡單的選擇可能更適合你。

5.5.1. 基本概要

ProxyFactoryBean,和其餘Spring的 FactoryBean實現同樣,引入一個間接的層次。若是你 定義一個名字爲foo的ProxyFactoryBean, 引用foo的對象所看到的不是ProxyFactoryBean 實例自己,而是由實現ProxyFactoryBean的類的 getObject()方法所建立的對象。這個方法將建立一個包裝了目標對象 的AOP代理。

使用ProxyFactoryBean或者其餘IoC可知的類來建立AOP代理 的最重要的優勢之一是IoC能夠管理通知和切入點。這是一個很是的強大的功能,可以實 現其餘AOP框架很難實現的特定的方法。例如,一個通知自己能夠引用應用對象(除了目標對象, 它在任何AOP框架中均可以引用應用對象),這徹底得益於依賴注入所提供的可插入性。

5.5.2. JavaBean的屬性

相似於Spring提供的絕大部分FactoryBean實現同樣, ProxyFactoryBean也是一個javabean,咱們能夠利用它的屬性來:

  • 指定你將要代理的目標

  • 指定是否使用CGLIB

一些關鍵屬性來自org.springframework.aop.framework.ProxyConfig :它是全部AOP代理工廠的父類。這些關鍵屬性包括:

  • proxyTargetClass: 若是咱們應該代理目標類, 而不是接口,這個屬性的值爲true。若是這是true,咱們須要使用CGLIB。

  • optimize: 是否使用強優化來建立代理。不要使用 這個設置,除非你瞭解相關的AOP代理是如何處理優化的。目前這隻對CGLIB代理有效;對JDK 動態代理無效(默認)。

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

  • exposeProxy: 當前代理是否要暴露在ThreadLocal中, 以便它能夠被目標對象訪問。(它能夠經過MethodInvocation獲得,不須要ThreadLocal)。 若是一個目標須要得到它的代理而且exposeProxy的值是ture,可使用 AopContext.currentProxy()方法。

  • aopProxyFactory: 所使用的AopProxyFactory具體實現。 這個參數提供了一條途徑來定義是否使用動態代理、CGLIB仍是其餘代理策略。默認實現將適當地選擇動態 代理或CGLIB。通常不須要使用這個屬性;它的意圖是容許Spring 1.1使用另外新的代理類型。

其餘ProxyFactoryBean特定的屬性包括:

  • proxyInterfaces: 接口名稱的字符串數組。若是這個 沒有提供,CGLIB代理將被用於目標類。

  • interceptorNames: Advisor、interceptor或其餘 被應用的通知名稱的字符串數組。順序是很重要的。這裏的名稱是當前工廠中bean的名稱,包 括來自祖先工廠的bean的名稱。

  • singleton: 工廠是否返回一個單獨的對象,不管 getObject()被調用多少次。許多FactoryBean 的實現提供這個方法。默認值是true。若是你想要使用有狀態的通知--例如,用於有狀態的 mixin--將這個值設爲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使用的,在當前bean工廠定義的interceptor或者advisor的 名字(advisor,interceptor,before,after returning,和throws advice 對象皆可)。 Advisor在該列表中的次序很重要。

你也許會對該列表爲何不採用bean的引用存有疑問。 緣由就在於若是ProxyFactoryBean的singleton屬性被設置爲false, 那麼bean工廠必須能返回多個獨立的代理實例。 若是有任何一個advisor自己是prototype的,那麼它就須要返回獨立的實例, 也就是有必要從bean工廠獲取advisor的不一樣實例,bean的引用在這裏顯然是不夠的。

上面定義的「person」bean定義能夠做爲Person接口的實現來使用,以下所示:

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實現。 可是,這個類多是一個動態代理類。也就是有可能把它類型轉換爲一個Advised接口 (該接口在下面的章節中論述) 。

5.5.4. 代理類

若是你須要代理的是類,而不是一個或多個接口,又該怎麼辦呢?

想象一下咱們上面的例子,若是沒有Person接口, 咱們須要通知一個叫Person的類, 並且該類沒有實現任何業務接口。在這種狀況下,你能夠配置Spring使用CGLIB代理, 而不是動態代理。你只要在上面的ProxyFactoryBean定義中把 它的proxyTargetClass屬性改爲true就好了。

只要你願意,即便在有接口的狀況下,你也能夠強迫Spring使用CGLIB代理。

CGLIB代理是經過在運行期產生目標類的子類來進行工做的。 Spring能夠配置這個生成的子類,來代理原始目標類的方法調用。這個子類是用 Decorator設計模式置入到advice中的。

CGLIB代理對於用戶來講應該是透明的。然而,還有如下一些因素須要考慮:

  • Final方法不能被通知,由於不能被重寫。

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

CGLIB和動態代理在性能上有微小的區別,對Spring 1.0來講,後者稍快。 另外,之後可能會有變化。在這種狀況下性能不是決定性因素

 

5.6. 便利的代理建立方式

一般,咱們不須要ProxyFactoryBean的所有功能,由於咱們經常只對一個方面感興趣: 例如,事務管理。

當咱們僅僅對一個特定的方面幹興趣時,咱們可使用許多便利的工廠來建立AOP代理。這些在其餘 章節討論,因此這裏咱們快速瀏覽一下它們。

5.6.1. TransactionProxyFactoryBean

用Spring提供的jPetStore的示例應用 演示了TransactionProxyFactoryBean的使用方式。

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

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

TransactionProxyFactoryBean須要設置一個target屬性, 還須要設置「transactionAttributes」, 「transactionAttributes」用來指定須要事務化 處理的方法,還有要求的傳播方式和其餘設置:

<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自動建立一個事務advisor, 該advisor包括一個基於事務屬性的切入點。所以只有事務性的方法被通知。

TransactionProxyFactoryBean使用preInterceptors和 postInterceptors屬性指定「pre」和「post」通知。它們將攔截器,通知和Advisor數組放置 在事務攔截器先後的攔截器鏈中。使用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定義裏。一個通用用法是將事務和聲明式 安全組合在一塊兒使用:一個和EJB提供的相似的方法。

由於使用前攔截器和後攔截器時,用的是真正的實例引用,而不象在 ProxyFactoryBean中用的bean的名字,所以它們只能用於共享實例的通知。 所以它們不能用在有狀態的通知中:例如,在mixin中。這和TransactionProxyFactoryBean的要求是 一致的。若是你須要更復雜的,能夠定製的AOP,你能夠考慮使用普通的ProxyFactoryBean, 或者是自動代理生成器(參考下面)。

尤爲是若是咱們將Spring的AOP在許多狀況下當作是EJB的替代品,咱們會發現大多數通知是很普通的, 可使用共享實例。聲明式的事務管理和安全檢查是一個典型的例子。

TransactionProxyFactoryBean依賴於由它的transactionManager 屬性指定的TransactionManager。 這種事務管理方式是可插拔的,基於JTA,JDBC或者其餘事務管理策略皆可。 這與Spring的事務抽象層有關,而不在於AOP自己。咱們將在下一章中討論事務機制。

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

5.6.2. EJB 代理

其它有一些專門的代理用於建立EJB代理,使得EJB的「業務方法」的接口能夠被調用代碼直接使用。 調用代碼並不須要進行JNDI查找或使用EJB的建立方法:這是在可讀性和架構靈活性方面的重大提升。

進一步請參考本手冊內的Spring的EJB業務。

 

5.7. 使用ProxyFactory以編程的方式建立AOP代理

使用Spring以編程的方式建立AOP代理也很簡單。 這使得你不須要Spring的IoC就可以使用Spring的AOP。

下面的代碼顯示的用攔截器和advisor爲目標對象建立代理。目標對象實現的接口將自動被代理:

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

第一步是建立類型爲org.springframework.aop.framework.ProxyFactory 的對象。你能夠和上面的例子同樣用目標對象建立,或者在另外一個構造函數中指定要被代理的接口。

你能夠添加攔截器或advisor,在整個ProxyFactory的生命週期內操做它們。若是你添加 IntroductionInterceptionAroundAdvisor,你可使代理實現附加接口。

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

將AOP代理的建立和IoC框架結合起來在大多數應用中都是最好的實現方式。咱們推薦你和通常狀況同樣, 不要將AOP配置信息放在Java代碼裏。

 

5.8. 操做被通知對象

不管你怎麼建立AOP代理,你均可以使用org.springframework.aop.framework.Advised 接口來操做它們。任何AOP代理不管實現其它什麼接口,均可以類型轉換爲這個接口。這個接口包括下列方法:

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,使用當前索引返回的advisor就是你添加的對象。若是你添加攔截器或其它通知類型, Spring將當前對象和一個知足要求的切入點封裝在一個advisor裏。所以,若是你添加 MethodInterceptor, 使用當前索引返回的advisor是一個DefaultPointcutAdvisor,這個advisor返回 MethodInterceptor和知足全部類和方法的切入點。

addAdvisor()被用來添加Advisor。一般會是一個普通的 DefaultPointcutAdvisor,它能夠和任何通知或切入點(除了引用)一塊兒使用。

缺省狀況下,在每次代理被建立的時候添加或刪除advisor或攔截器。惟一的限制是不能添加或刪除 引入advisor,由於工廠提供的已存在的代理不反映接口的變化。(你能夠從工廠獲得一個新的代理來避免這個問題)

是否建議在產品中修改業務對象的通知還值得懷疑,雖然毫無疑問存在合理的使用狀況。可是, 在開發中這是很是有用的:例如,在測試中。我有時候發現以攔截器或其它通知的形式來添加測試代碼很是有用, 這樣就能夠進入我想要測試的方法調用。(例如,通知能夠進入爲這個方法建立的事務中: 在爲回滾事務做標記前,運行SQL檢查數據庫是否被正確更新。)

根據你建立代理的方式,你一般能夠設置frozen標記,這樣Advised 的isFrozen()就返回true,任何添加或刪除通知都將致使 AopConfigException。這種凍結被通知對象狀態的方法在一些狀況下是很是有用的: 例如,爲了阻止調用代碼刪除一個安全攔截器。若是已知運行時修改通知不被容許,這還能夠被Spring 1.1用來 做優化。

 

5.9. 使用「autoproxy」功能

目前爲止,咱們已經討論了使用ProxyFactoryBean或相似的工廠bean來顯式建立AOP代理。

Spring也容許咱們使用「autoproxy」的bean定義,它能夠自動代理所選擇的bean定義。這是創建在Spring的 「bean後處理器」機制上的,它可以在容器載入bean定義的時候修改任何bean定義。

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

有兩種方法來實現自動代理:

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

  • 有一個特殊的自動代理建立的狀況值得單獨考慮:由源代碼級元數據驅動的自動代理建立

5.9.1. 自動代理的bean定義

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

5.9.1.1. BeanNameAutoProxyCreator

BeanNameAutoProxyCreator爲名字符合某個值或統配符的bean自動建立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或任何通知類型。

就象通常的自動代理建立同樣,使用BeanNameAutoProxyCreator的主要目的 是對多個對象使用相同的配置信息,而且減小配置的工做量。這在爲多個對象使用聲明式事務時是一個很流行的選擇。

在上面的例子中,名字匹配的bean定義,如「jdkMyBean」和「onlyJdk」,是包含目標類的普通bean定義。 BeanNameAutoProxyCreator將自動建立AOP代理。相同的通知會被因用到全部匹配的bean。 注意,若是使用了advisor(而不是上面例子中的攔截器),切入點可能對不一樣的bean會不一樣。

5.9.1.2. DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator是一個更通用,更強大的自動代理生成器。它將 自動應用於當前上下文的符合條件的advisor,而不須要在自動代理advisor的bean定義中包含特定的bean名字。 它有助於配置的一致性,並避免象BeanNameAutoProxyCreator同樣重複配置。

使用這個機制包括:

  • 指定一個DefaultAdvisorAutoProxyCreator的bean定義

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

DefaultAdvisorAutoProxyCreator會自動計算每一個advisor包含的的切入點,看看 是否有什麼通知應該被引用到每一個業務對象(好比例子中的「businessObject1」和「businessObject2」)。

這意味着任何數目的advisor均可以自動應用到每一個業務對象。若是advisor中沒有任何切入點符合業務對象的 方法,這個對象就不會被代理。由於會爲新的業務對象添加bean定義,若是必要,它們會自動被代理。

通常來講,自動代理能夠保證調用者或依賴沒法接觸未被通知的對象。在這個ApplicationContext上 調用getBean("businessObject1")返回一個AOP代理,而不是目標業務對象。

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

若是你想在幾個業務對象上應用相同的通知,DefaultAdvisorAutoProxyCreator 就很是有用。一旦定義恰當,你能夠簡單地添加業務對象而不須要包括特定的代理配置。你也能夠很是容易地 刪除所附加的方面--例如,跟蹤或性能監控的方面--能夠儘量減小配置修改。

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

5.9.1.3. AbstractAdvisorAutoProxyCreator

這是DefaultAdvisorAutoProxyCreator的父類。你能夠繼承它實現你本身的自動代理生成器,這種狀況不太常見, 通常是advisor定義不能給DefaultAdvisorAutoProxyCreator框架的行爲提供足夠的定製。

5.9.2. 使用元數據驅動的自動代理

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

在這種狀況下,你可使用DefaultAdvisorAutoProxyCreator,以及能夠讀取元數據屬性的 Advisor。元數據細節定義在候選advisor的切入點部分,而不是自動代理建立類自己。

這是DefaultAdvisorAutoProxyCreator的一種特殊狀況,可是它自己而言是值得考慮的。 (能夠讀取元數據的代碼處於advisor的切入點中,而不是AOP框架自己。)

jPetStore示例應用的/attributes目錄演示了屬性驅動的自動代理的使用。在這個例子中, 沒有必要使用TransactionProxyFactoryBean。僅僅在業務對象上定義業務屬性就足夠了,由於 使用了可知元數據的切入點。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」,可是名字 可有可無--會在當前的應用上午中選擇全部符合的切入點。在這個例子中,類型爲 TransactionAttributeSourceAdvisor的「transactionAdvisor」bean定義將會應用於包含事務屬性 的類或方法。TransactionAttributeSourceAdvisor經過構造函數依賴於TransactionInterceptor。這個例子經過自動 裝配來解析它。AttributesTransactionAttributeSource依賴於 org.springframework.metadata.Attributes接口的一個實現。在這段代碼中,「attributes」 bean使用Jakarta Commons Attributes API來獲取屬性信息。(應用代碼必須使用Commons Attributes編譯任務編譯。)

這裏定義的TransactionInterceptor依賴於一個 PlatformTransactionManager定義,它並無被包括在這個通用的文件中(雖然應該是這樣), 這是由於它是和應用的事務需求相關的(通常地,是想這個例子中的JTA,或者Hibernate,JDO 或JDBC):

<bean id="transactionManager" 
    class="org.springframework.transaction.jta.JtaTransactionManager"/>
若是你只要求聲明式事務管理,使用這些通用的XML定義就可使得Spring自動代理含有事務屬性的全部類和方法。 你不須要直接和AOP打交道,而且編程模型和.NET的ServicedComponents很是類似。

這個機制具備可擴展性。它能夠基於定製的屬性來使用自動代理。你須要:

  • 定義你的定製屬性。

  • 指定的Advisor包含必要的通知和由方法或類的定製屬性所觸發的切入點。你可使用已經存在的通知,僅僅實 現用來選擇定製屬性的切入點。

這些advisor可能對每一個被通知類都是惟一的(例如,maxin)。它們僅僅須要被定義成 prototype bean,而不是singleton bean。例如,Spring的測試套件中的LockMixin 引入攔截器能夠和一個屬性驅動切入點一塊兒來定位一個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 將被應用。注意,lockMixin和lockableAdvisor定義都是 prototype的。myAttributeAwarePointcut切入點能夠被定義成singleton,由於它不爲 不一樣的被通知對象保存狀態。

 

5.10. 使用TargetSources

Spring提供了TargetSource的概念,由 org.springframework.aop.TargetSource接口定義。這個接口負責返回實現切入點的 「目標對象」。每次AOP代理處理方法調用時,目標實例都會用到TargetSource實現。

使用Spring AOP的開發者通常不須要直接使用TargetSources,可是這提供了一種強大的方法來支持池,熱交換, 和其它複雜目標。例如,一個支持池的TargetSource能夠在每次調用時返回不一樣的目標對象實例,使用池來管理實例。

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

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

當使用定製目標源時,你的目標一般須要定義爲prototype bean,而不是singleton bean。這使得 Spring在須要的時候建立一個新的目標實例。

5.10.1. 可熱交換的目標源

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>

<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()調用會修改swappable這個bean的目標。持有對這個bean應用的客戶端 將不會知道這個變化,但會馬上轉爲使用新的目標對象。

雖然這個例子沒有添加任何通知--使用TargetSource也不必添加通知-- 固然任何TargetSource均可以和任何一種通知一塊兒使用。

5.10.2. 支持池的目標源

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

Spring的池和SLSB的池之間的重要區別在於Spring的池能夠被應用到任何普通Java對象。就象Spring的通用 的作法,這個業務也能夠以非侵入的方式被應用。

Spring直接支持Jakarta Commons Pool 1.1,它是一種很是高效的池實現。使用這個功能,你須要在你的應用的 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的實如今擴大池容量的時候能夠建立目標的新實例。關於這些屬性的 信息能夠參考AbstractPoolingTargetSource和子類的Javadoc。maxSize是最基本的屬性, 被保證老是存在。

在這種狀況下,名字爲「myInterceptor」的攔截器須要定義在同一個IoC上下文中。可是,並不必定須要 指定攔截器也用池。若是你僅須要池,而且沒有其它通知,能夠根本不設置屬性interceptorNames。

也能夠配置Spring以即可以將任何池化的對象轉換類型爲 org.springframework.aop.target.PoolingConfig接口。經過這個接口的一個引入,能夠獲得 配置信息和池的當前大小。你須要這樣定義一個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>

經過調用AbstractPoolingTargetSource類上的方法,能夠獲得這個advisor, 所以使用MethodInvokingFactoryBean。這個advisor的名字(「poolConfigAdvisor」)必須在暴露池化對象的 This advisor is obtained by calling a convenience method on the ProxyFactoryBean中的攔截器名字列表中。

這個類型轉換就象下面:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
池化無狀態業務對象並非老是必要的。咱們不認爲這是缺省選擇,由於大多數無狀態對象天然就是線程 安全的,若是資源被緩存,實例池化會有問題。

簡單的池也可使用自動代理。任何自動代理生成器均可以設置TargetSources。

5.10.3. Prototype目標源

設置「prototype」目標源和支持池的目標源相似。在每次方法調用的時候都會建立一個新的目標實例。 雖然在現代JVM中建立對象的代價不是很高,可是裝配新對象的代價可能更高(爲了maz知足它的IoC依賴關係)。 所以沒有好的理由不該該使用這個方法。

爲了這麼作,你能夠修改上面的的poolTargetSource定義,就向下面同樣。 (爲了清晰起見,我修改了名字。)

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

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

 

5.11. 定義新的通知類型

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

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

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

 

5.12. 進一步的資料和資源

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

進一步的Spring AOP的例子請參考Spring的示例應用:

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

  • JPetStore的/attributes目錄演示了屬性驅動的聲明式事務管理。

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

 

5.13. 路標

Spring AOP,就象Spring的其它部分,是開發很是活躍的部分。核心API已經穩定了。象Spring的其它部分同樣, AOP框架是很是模塊化的,在保留基礎設計的同時提供擴展。在Spring 1.1到1.2階段有不少地方可能會有所提升,可是這 些地方也保留了向後兼容性。它們是:

  • 性能的提升:AOP代理的建立由工廠經過策略接口處理。所以咱們可以支持額外的AOP 代理類型而不影響用戶代碼或核心實現。對於Spring 1.1,咱們正在檢查AOP代理實現的全部字節碼,萬一不須要 運行時通知改變。這應該大大減小AOP框架的額外操做。可是注意,AOP框架的額外操做不是在普通使用中須要考慮 的內容。

  • 更具表達力的切入點:Spring目前提供了一個具備表達力的切入點接口,可是咱們 添加更多的切入點實現。咱們正在考慮提供一個簡單但具備強大表達式語言的實現。若是你但願貢獻一個有用的 切入點實現,咱們將很是歡迎。

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

相關文章
相關標籤/搜索