天天看點

Spring AOP API詳解

作者:DOKER

上一章介紹了Spring對AOP的支援,包括@AspectJ和基于schema的切面定義。在這一章中,我們将讨論低級别的Spring AOP API。對于普通的應用,我們推薦使用前一章中描述的帶有AspectJ pointcuts 的Spring AOP。

6.1. Spring 中的 Pointcut API

這一節描述了Spring如何處理關鍵的 pointcut 概念。

6.1.1. 概念

Spring的pointcut模型使pointcut重用與advice類型無關。你可以用相同的pointcut來針對不同的advice。

org.springframework.aop.Pointcut 接口是中心接口,用于為特定的類和方法提供advice。完整的接口如下。

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}
           

将 Pointcut 接口分成兩部分,允許重用類和方法比對部分以及細粒度的組合操作(比如與另一個方法比對器進行 "聯合")。

ClassFilter 接口被用來将該pointcut限制在一組給定的目标類中。如果 matches() 方法總是傳回 true,所有的目标類都被比對。下面的清單顯示了 ClassFilter 接口的定義。

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) 方法用來測試這個pointcut是否曾經比對過目标類上的一個給定的方法。這個評估可以在建立AOP代理時進行,以避免在每個方法調用時進行測試。如果雙參數 matches 方法對一個給定的方法傳回 true,并且 MethodMatcher 的 isRuntime() 方法傳回 true,那麼三參數 matches 方法将在每個方法調用中被調用。這讓一個pointcut在目标 advice 開始之前立即檢視傳遞給方法調用的參數。

大多數 MethodMatcher 實作是靜态的,這意味着它們的 isRuntime() 方法傳回 false。在這種情況下,三參數 matches 方法從未被調用。

如果可能的話,盡量使 pointcuts 成為靜态的,允許AOP架構在建立AOP代理時緩存 pointcuts 評估的結果。

6.1.2. 對 Pointcut 的操作

Spring支援對 pointcut 的操作(尤其是 union 和 intersection)。

Union指的是任何一個pointcut比對的方法。Intersection指的是兩個pointcut都比對的方法。Union通常更有用。你可以通過使用 org.springframework.aop.support.Pointcuts 類中的靜态方法或通過使用同一包中的 ComposablePointcut 類來組合pointcuts。然而,使用AspectJ的pointcut表達式通常是一種更簡單的方法。

6.1.3. AspectJ 表達式的 Pointcuts

從2.0開始,Spring使用的最重要的pointcut類型是 org.springframework.aop.aspectj.AspectJExpressionPointcut。這是一個使用AspectJ提供的庫來解析AspectJ pointcut表達式字元串的pointcut。

關于支援的 AspectJ pointcut primitives 的讨論見 前一章。

6.1.4. 友善的 Pointcut 實作

Spring提供了幾種友善的pointcut實作。你可以直接使用其中的一些;其他的則打算在特定于應用的 pointcut 進行子類化。

靜态 Pointcut

靜态pointcut是基于方法和目标類的,不能考慮到方法的參數。對于大多數的使用來說,靜态的pointcut已經足夠了,而且是最好的。Spring隻能在一個方法第一次被調用時評估一次靜态的pointcut。在那之後,就不需要在每次調用方法時再次評估該pointcut了。

本節的其餘部分描述了Spring中包含的一些靜态pointcut實作。

正規表達式 Pointcut

一個明顯的指定靜态pointcut的方法是正規表達式。org.springframework.aop.support.JdkRegexpMethodPointcut 是一個通用的正規表達式 pointcut,它使用JDK中的正規表達式支援。

通過 JdkRegexpMethodPointcut 類,你可以提供一個pattern字元串的清單。如果其中任何一個是比對的,那麼這個pointcut就會被評估為 true。(是以,産生的 pointcut 實際上是指定 pattern 的 union。)

下面的例子顯示了如何使用 JdkRegexpMethodPointcut。

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

Spring提供了一個名為 RegexpMethodPointcutAdvisor 的友善類,它讓我們也可以引用一個 Advice(記住,一個 Advice 可以是一個攔截器、before advice、throws advice以及其他)。在幕後,Spring使用 JdkRegexpMethodPointcut。使用 RegexpMethodPointcutAdvisor 可以簡化裝配,因為這個bean同時封裝了 Pointcut 和 Advice,正如下面的例子所示。

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

你可以将 RegexpMethodPointcutAdvisor 用于任何 Advice 類型。

屬性驅動 Pointcuts

靜态pointcut的一個重要類型是中繼資料驅動的pointcut。它使用中繼資料屬性的值(通常是源級中繼資料)。

動态 pointcuts

動态 pointcut 比靜态 pointcut 的評估成本更高。它們考慮到了方法的參數以及靜态資訊。這意味着它們必須在每次調用方法時被評估,而且結果不能被緩存,因為參數會變化。

主要的例子是 control flow pointcut。

Control Flow Pointcut

Spring control flow pointcut 在概念上與AspectJ的 cflow pointcut相似,但功能較弱。(目前還沒有辦法指定一個pointcut在另一個pointcut所比對的連接配接點下面運作)。一個 control flow pointcut 與目前的調用棧相比對。例如,如果連接配接點被 com.mycompany.web 包中的方法或 SomeCaller 類所調用,它就會啟動。Control flow pointcut 是通過使用 org.springframework.aop.support.ControlFlowPointcut 類指定的。

Control flow pointcut 在運作時的評估成本甚至比其他動态pointcut高得多。在Java 1.4中,其成本大約是其他動态pointcut的五倍。

6.1.5. Pointcut 超類

Spring提供了有用的 pointcut 超類來幫助你實作你自己的pointcut。

因為靜态pointcut是最有用的,是以你可能應該子類化 StaticMethodMatcherPointcut。這隻需要實作一個抽象方法(盡管你可以覆寫其他方法來定制行為)。下面的例子展示了如何子類化 StaticMethodMatcherPointcut。

Java

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

也有一些超類用于動态的 pointcut。你可以在任何 advice 類型中使用自定義的 pointcut。

6.1.6. 自定義 Pointcut

因為Spring AOP中的 pointcut 是Java類,而不是語言特性(如AspectJ),是以你可以聲明自定義pointcut,無論是靜态還是動态。在Spring中,自定義的pointcut可以是任意複雜的。然而,如果可以的話,我們建議使用AspectJ的pointcut表達語言。

後來的Spring版本可能會提供對JAC所提供的 "語義 pointcut" 的支援—例如,"所有改變目标對象中執行個體變量的方法"。

6.2. Spring 的 Advice API

現在我們可以研究Spring AOP如何處理advice。

6.2.1. Advice 生命周期

每個 advice 都是一個Spring Bean。一個 advice 執行個體可以在所有 advice 對象中共享,也可以對每個 advice 對象是唯一的。這對應于每類或每執行個體的 advice。

按類 advice 是最常使用的。它适合于通用advice,如事務advice。這些advice不依賴于被代理對象的狀态或添加新的狀态。他們隻是對方法和參數采取行動。

每個執行個體的advice适合于introduction,以支援混搭。在這種情況下,advice将狀态添加到被代理的對象中。

你可以在同一個AOP代理中混合使用共享和每執行個體advice。

6.2.2. Spring 的 Advice Type

Spring提供了幾種 advice 類型,并可擴充到支援任意的 advice 類型。本節介紹了基本概念和标準advice類型。

Interception Around Advice

Spring中最基本的advice type 是 interception around advice。

Spring 符合 AOP Alliance 對使用方法攔截的 around advice 的接口。實作 MethodInterceptor 和實作 around advice 的類也應該實作以下接口。

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}
           

invoke() 方法的 MethodInvocation 參數暴露了被調用的方法、目标連接配接點、AOP 代理以及方法的參數。invoke() 方法應該傳回調用的結果:連接配接點的傳回值。

下面的例子顯示了一個簡單的 MethodInterceptor 實作。

Java

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 advice 一樣,可以傳回一個不同的值或抛出一個異常,而不是調用 proceed 方法。然而,如果沒有充分的理由,你不希望這樣做。

MethodInterceptor 的實作提供了與其他符合AOP聯盟的AOP實作的互操作性。本節其餘部分讨論的其他advice類型實作了常見的AOP概念,但以Spring特有的方式實作。雖然使用最具體的advice類型有好處,但如果你可能想在另一個AOP架構中運作該切面,請堅持使 around advice 的 MethodInterceptor。請注意,目前 pointcut 在各架構之間是不能互通的,AOP聯盟目前也沒有定義 pointcut 接口。

Before Advice

一個更簡單的advice類型是 before advice。這不需要一個 MethodInvocation 對象,因為它隻在進入方法之前被調用。

Before advice 的主要優點是不需要調用 proceed() 方法,是以也就不可能在不經意的情況下無法順着攔截器鍊進行。

下面的清單顯示了 MethodBeforeAdvice 接口。

public interface MethodBeforeAdvice extends BeforeAdvice {

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

(Spring的API設計将允許字段 before advice,盡管通常的對象适用于字段攔截,Spring不太可能實作它)。

注意,void 類型是無效的。Before advice 可以在連接配接點運作前插入自定義行為,但不能改變傳回值。如果一個 before advice 抛出一個異常,它就會停止攔截器鍊的進一步執行。異常會在攔截器鍊上向後傳播。如果它沒有被選中或在被調用方法的簽名上,它将直接傳遞給用戶端。否則,它将被AOP代理包裹在一個未檢查的異常中。

下面的例子顯示了Spring中的before建議,它計算了所有方法的調用。

Java

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 advice,可以與任何pointcut一起使用。

Throws Advice

如果連接配接點抛出了一個異常,Throws advice 會在連接配接點傳回後被調用。Spring提供了類型化的throws advice。注意,這意味着 org.springframework.aop.ThrowsAdvice 接口不包含任何方法。它是一個标簽接口,辨別給定對象實作了一個或多個類型化的 throws advice 方法。這些方法應該是以下形式。

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

隻有最後一個參數是必須的。方法簽名可以有一個或四個參數,這取決于advice方法是否對方法和參數感興趣。接下來的兩個清單顯示了作為 throws advice 的例子的類。

如果抛出了 RemoteException(包括來自子類的),将調用以下 advice。

Java

public class RemoteThrowsAdvice implements ThrowsAdvice {

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

與前面的advice不同,下一個例子聲明了四個參數,這樣它就可以通路被調用的方法、方法參數和目标對象。如果抛出一個 ServletException,下面的advice會被調用。

Java

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最後一個例子說明了如何在一個同時處理 RemoteException 和 ServletException 的單一類中使用這兩種方法。任何數量的 throws advice 方法都可以在一個類中結合使用。下面的清單顯示了最後的例子。

Java

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 with all arguments
    }
}
           
如果一個 throws-advice 方法自己抛出了一個異常,它就會覆寫原來的異常(也就是說,它改變了抛給使用者的異常)。覆寫的異常通常是一個 RuntimeException,它與任何方法的簽名相容。然而,如果一個throws-advice方法抛出一個被檢查的異常,它必須與目标方法的聲明異常相比對,是以,在某種程度上與特定的目标方法簽名相耦合。不要抛出一個與目标方法簽名不相容的未聲明的檢查性異常!
Throws advice 可以與任何 pointcut 一起使用。

After Returning Advice

Spring 中的 after returning advice 必須實作 org.springframework.aop.AfterReturningAdvice 接口,如下清單所示。

public interface AfterReturningAdvice extends Advice {

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

一個 after returning advice 可以通路傳回值(它不能修改)、被調用的方法、方法的參數和目标。

以下是 after returning advice 對所有沒有抛出異常的成功方法調用進行統計。

Java

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

這個advice不會改變執行路徑。如果它抛出一個異常,它将被抛向攔截器鍊,而不是傳回值。

After returning advice 可以與任何 pointcut 一起使用。

Introduction Advice

Spring 把 interception advice 當作一種特殊的 introduction advice。

Introduction 需要一個 IntroductionAdvisor 和一個 IntroductionInterceptor 來實作以下接口。

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
           

從AOP聯盟 MethodInterceptor 接口繼承的 invoke() 方法必須實作引入。也就是說,如果被調用的方法在一個引入接口上,引入攔截器負責處理方法調用—​它不能調用 proceed()。

Introduction advice 能被用于任何 pointcut,因為它隻适用于類,而不是方法層面。你隻能在 IntroductionAdvisor 中使用 introduction advice,它有以下方法。

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
           

沒有 MethodMatcher,是以也沒有與 introduction advice 相關的 Pointcut。隻有類的過濾是合乎邏輯的。

getInterfaces() 方法傳回該 advice 所引入的接口。

validateInterfaces() 方法在内部使用,以檢視引入的接口是否能被配置的 IntroductionInterceptor 實作。

考慮一下Spring測試套件中的一個例子,假設我們想為一個或多個對象引入以下接口。

Java

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

這說明了一個混合器。我們希望能夠将advice 的對象轉為 Lockable,無論其類型如何,并調用 lock 和 unlock 方法。如果我們調用 lock() 方法,我們希望所有的 setter 方法都抛出一個 LockedException。是以,我們可以添加一個切面,提供使對象不可變的能力,而他們對此一無所知:這是AOP的一個好例子。

首先,我們需要一個 IntroductionInterceptor 來完成繁重的工作。在這種情況下,我們擴充 org.springframework.aop.support.DelegatingIntroductionInterceptor 便利類。我們可以直接實作 IntroductionInterceptor,但在大多數情況下,使用 DelegatingIntroductionInterceptor 是最好的。

DelegatingIntroductionInterceptor 被設計成将介紹委托給所介紹的(introduced)接口的實際實作,隐蔽地使用攔截來做到這一點。你可以使用構造函數參數将委托設定為任何對象。預設的委托(當使用無參數構造函數時)是 this。是以,在下一個例子中,委托是 DelegatingIntroductionInterceptor 的 LockMixin 子類。給定一個委托(預設情況下是它自己),DelegatingIntroductionInterceptor 執行個體尋找由委托實作的所有接口(除 IntroductionInterceptor 外),并支援針對其中任何接口的引入。像 LockMixin 這樣的子類可以調用 suppressInterface(Class intf) 方法來抑制那些不應該被暴露的接口。然而,不管一個 IntroductionInterceptor 準備支援多少個接口,所使用的 IntroductionAdvisor 控制着哪些接口被實際暴露。一個引入的接口隐藏了目标對同一接口的任何實作。

是以,LockMixin 擴充了 DelegatingIntroductionInterceptor 并實作了 Lockable 本身。超類會自動捕捉到 Lockable 可以被支援用于引入,是以我們不需要指定。我們可以用這種方式引入(introduce)任何數量的接口。

注意 locked 執行個體變量的使用。這有效地增加了目标對象中持有的額外狀态。

下面的例子顯示了 LockMixin 類的例子。

Java

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 實作(如果方法被引入,它就調用 delegate 方法,否則就向連接配接點前進)就足夠了。在本例中,我們需要添加一個檢查:如果處于鎖定模式,則不能調用任何setter方法。

所需的引入(introduction)隻需要持有一個獨特的 LockMixin 執行個體并指定引入的接口(在本例中,隻有 Lockable)。一個更複雜的例子可能需要一個對引入攔截器的引用(它将被定義為一個prototype)。在這種情況下,沒有與 LockMixin 相關的配置,是以我們用 new 來建立它。下面的例子展示了我們的 LockMixinAdvisor 類。

Java

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我們可以非常簡單地應用這個advice,因為它不需要配置。(然而,沒有 IntroductionAdvisor 就不可能使用 IntroductionInterceptor)。像通常的介紹(introduction)一樣,顧問必須是按執行個體的,因為它是有狀态的。我們需要一個不同的 LockMixinAdvisor 執行個體,進而為每個被advice的對象提供 LockMixin。advisor包括被advice對象的部分狀态。

我們可以通過使用 Advised.addAdvisor() 方法或(推薦的方式)在 XML 配置中,像任何其他 advice 一樣,以程式設計方式應用這個advice。下面讨論的所有代理建立選擇,包括 "自動代理建立者",都能正确處理introduction和有狀态的混合(mixin)。

6.3. Spring 中的 Advisor API

在Spring中,Advisor 是一個切面,它隻包含一個與 pointcut 表達式相關聯的單個advice對象。

除了introduction的特殊情況外,任何 advisor 都可以與任何advice一起使用。org.springframework.aop.support.DefaultPointcutAdvisor 是最常用的advisor類。它可以與 MethodInterceptor、BeforeAdvice 或 ThrowsAdvice 一起使用。

在同一個AOP代理中,有可能混合advisor和advice類型。例如,你可以在一個代理配置中使用interception around advice,throws advice 以及 before advice。Spring會自動建立必要的攔截器鍊。

6.4. 使用ProxyFactoryBean建立AOP代理

如果你為你的業務對象使用Spring IoC容器(ApplicationContext 或 BeanFactory)(你應該這樣做!),你想使用Spring的AOP FactoryBean 實作之一。(記住,factory Bean引入了一層中介,讓它建立不同類型的對象)。

Spring的AOP支援也使用了 factory bean。

在Spring中建立AOP代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean。這樣就可以完全控制pointcut、任何适用的advice以及它們的排序。然而,如果你不需要這樣的控制,也有一些更簡單的選項是更好的。

6.4.1. 基礎知識

ProxyFactoryBean 和其他Spring FactoryBean 的實作一樣,引入了一定程度的間接性。如果你定義了一個名為 foo 的 ProxyFactoryBean,那麼引用 foo 的對象看到的不是 ProxyFactoryBean 執行個體本身,而是一個由 ProxyFactoryBean 中 getObject() 方法的實作所建立的對象。這個方法建立了一個AOP代理,包裹了一個目标對象。

使用 ProxyFactoryBean 或其他IoC感覺(aware)類來建立AOP代理的一個最重要的好處是,advice和pointcuts也可以由IoC管理。這是一個強大的功能,可以實作其他AOP架構難以實作的某些方法。例如,advice本身可以引用應用對象(除了目标,任何AOP架構都應該有),受益于依賴注入提供的所有可插拔性。

6.4.2. JavaBean Properties

與Spring提供的大多數 FactoryBean 實作一樣,ProxyFactoryBean 類本身就是一個JavaBean。它的屬性被用來:

  • 指定你要代理的目标。
  • 指定是否使用CGLIB(稍後描述,另見 基于 JDK 和 CGLIB 的代理)。

一些關鍵屬性是從 org.springframework.aop.framework.ProxyConfig(Spring中所有AOP代理工廠的超類)繼承的。這些關鍵屬性包括以下内容。

  • proxyTargetClass: 如果目标類要被代理,而不是目标類的接口,則為 true。如果這個屬性值被設定為 true,那麼就會建立CGLIB代理(但也請看 基于 JDK 和 CGLIB 的代理)。
  • optimize: 控制是否将積極的優化應用于通過CGLIB建立的代理。你不應該輕率地使用這個設定,除非你完全了解相關的AOP代理如何處理優化。目前這隻用于CGLIB代理。它對JDK動态代理沒有影響。
  • frozen: 如果一個proxy configuration 當機(frozen),就不再允許對配置進行更改。這既是一種輕微的優化,也适用于那些不希望調用者在代理建立後能夠操作代理(通過 Advised 接口)的情況。這個屬性的預設值是 false,是以允許改變(比如添加額外的 advice)。
  • exposeProxy: 确定目前代理是否應在 ThreadLocal 中暴露,以便它可以被目标通路。如果目标需要獲得代理,并且 exposeProxy 屬性被設定為 true,那麼目标可以使用 AopContext.currentProxy() 方法。

ProxyFactoryBean 的其他特定屬性包括如下。

  • proxyInterfaces: 一個 String 接口名稱的數組。如果不提供這個,就會使用目标類的CGLIB代理(但也請看 基于 JDK 和 CGLIB 的代理)。
  • interceptorNames: 一個由 Advisor、攔截器或其他advice名稱組成的 String 數組,以便應用。排序是很重要的,以先來後到為原則。也就是說,清單中的第一個攔截器是第一個能夠攔截調用的。
  • 這些名字是目前工廠的Bean名稱,包括來自祖先工廠的Bean名稱。你不能在這裡提到Bean引用,因為這樣做會導緻 ProxyFactoryBean 忽略advice的singleton設定。
  • 你可以在攔截器的名字後面加上星号(*)。這樣做的結果是應用所有advisor bean,其名稱以星号前的部分開始,将被應用。你可以在 使用 “全局” (Global)Advisor 中找到使用這一功能的例子。
  • singleton: 無論 getObject() 方法被調用多少次,工廠是否應該傳回一個單一對象。一些 FactoryBean 的實作提供了這樣的方法。預設值是 true。如果你想使用有狀态的advice—​例如,對于有狀态的混合器(mixin)--請使用 prototype advice和 false 的 singleton 值。

6.4.3. 基于 JDK 和 CGLIB 的代理

本節是關于 ProxyFactoryBean 如何選擇為特定目标對象(要代理的對象)建立基于JDK的代理或基于CGLIB的代理的權威文檔。

ProxyFactoryBean 在建立基于JDK或CGLIB的代理方面的行為在Spring的1.2.x和2.0版本之間有所改變。現在 ProxyFactoryBean 在自動檢測接口方面表現出與 TransactionProxyFactoryBean 類相似的語義。

如果要代理的目标對象的類(以下簡稱目标類)沒有實作任何接口,就會建立一個基于 CGLIB 的代理。這是最簡單的情況,因為JDK代理是基于接口的,而沒有接口意味着JDK代理甚至不可能。你可以插入目标Bean,并通過設定 interceptorNames 屬性指定攔截器清單。注意,即使 ProxyFactoryBean 的 proxyTargetClass 屬性被設定為 false,也會建立基于CGLIB的代理。(這樣做沒有意義,最好從Bean 定義(definition)中删除,因為它最好是多餘的,最壞的情況是令人困惑。)

如果目标類實作了一個(或多個)接口,建立的代理類型取決于 ProxyFactoryBean 的配置。

如果 ProxyFactoryBean 的 proxyTargetClass 屬性被設定為 true,就會建立一個基于CGLIB的代理。這是有道理的,并且符合最小驚喜的原則。即使 ProxyFactoryBean 的 proxyInterfaces 屬性被設定為一個或多個全路徑的接口名稱,proxyTargetClass 屬性被設定為 true 這一事實也會導緻基于CGLIB的代理的生效。

如果 ProxyFactoryBean 的 proxyInterfaces 屬性被設定為一個或多個全路徑的接口名稱,就會建立一個基于 JDK 的代理。建立的代理實作了在 proxyInterfaces 屬性中指定的所有接口。如果目标類恰好實作了比 proxyInterfaces 屬性中指定的更多的接口,那就好辦了,但這些額外的接口不會被傳回的代理所實作。

如果 ProxyFactoryBean 的 proxyInterfaces 屬性沒有被設定,但目标類确實實作了一個(或多個)接口,ProxyFactoryBean 會自動檢測目标類确實實作了至少一個接口這一事實,并建立一個基于JDK的代理。實際上被代理的接口是目标類實作的所有接口。實際上,這與向 proxyInterfaces 屬性提供目标類實作的每一個接口的清單是一樣的。然而,這明顯減少了工作量,也不容易出現排版錯誤。

6.4.4. 代理接口

考慮一下 ProxyFactoryBean 在 action 中的一個簡單例子。這個例子涉及到:

  • 一個被代理的目标Bean。這就是例子中的 personTarget Bean定義。
  • 一個 Advisor 和一個 Interceptor 用來提供 advice。
  • 一個AOP代理Bean的定義,用來指定目标對象(personTarget Bean),代理的接口,以及應用的advice。

下面的清單顯示了這個例子。

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</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"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>           

請注意,interceptorNames 屬性接收一個 String 清單,其中儲存了目前工廠中的攔截器或advisor的bean名稱。你可以使用advisor、攔截器、before、after returning 和 throws 的advice對象。advisor 的排序是很重要的。

你可能想知道為什麼清單中沒有保留Bean的引用。原因是,如果 ProxyFactoryBean 的 singleton 屬性被設定為 false,它必須能夠傳回獨立的代理執行個體。如果任何一個 advisor 本身是一個prototype,就需要傳回一個獨立的執行個體,是以必須能夠從工廠獲得一個 prototype 的執行個體。持有一個引用是不夠的。

前面顯示的 person bean定義可以用來代替 Person 的實作,如下所示。

Java

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

同一IoC上下文中的其他Bean可以表達對它的強類型依賴,就像對普通Java對象一樣。下面的例子展示了如何做到這一點。

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

本例中的 PersonUser 類暴露了一個 Person 類型的屬性。就它而言,AOP代理可以透明地用來代替 "真正的" person 的實作。然而,它的類将是一個動态代理類。有可能把它強制轉換為 Advised 接口(後面讨論)。

你可以通過使用一個匿名的内部Bean來掩蓋目标和代理之間的差別。隻有 ProxyFactoryBean 的定義是不同的。包括這個建議隻是為了完整。下面的例子展示了如何使用一個匿名的内部Bean。

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>           

使用匿名内部Bean的好處是隻有一個 Person 類型的對象。如果我們想防止應用程式上下文的使用者獲得對未被 advice 的對象的引用,或者需要避免與Spring IoC autowiring 的任何歧義,這就很有用。可以說,還有一個好處是 ProxyFactoryBean 的定義是自成一體的。然而,有時能夠從工廠中獲得未被 advice 的目标實際上是一種優勢(例如,在某些測試場景中)。

6.4.5. 代理類

如果你需要代理一個類,而不是一個或多個接口怎麼辦?

想象一下,在我們之前的例子中,并沒有 Person 接口。我們需要建議一個名為 Person 的類,它沒有實作任何業務接口。在這種情況下,你可以将Spring配置為使用CGLIB代理而不是動态代理。要做到這一點,将前面所示的 ProxyFactoryBean 上的 proxyTargetClass 屬性設定為 true。雖然最好是對接口而不是類進行程式設計,但在處理遺留代碼時,為沒有實作接口的類提供 advice 的能力還是很有用的。(一般來說,Spring不是規定性的。雖然它使應用良好的實踐變得容易,但它避免了強迫一種特定的方法)。

如果你願意,你可以在任何情況下強制使用CGLIB,即使你确實有接口。

CGLIB代理的工作方式是在運作時生成一個目标類的子類。Spring對這個生成的子類進行配置,将方法調用委托給原始目标。這個子類被用來實作Decorator模式,在其中advice中織入。

CGLIB 代理通常對使用者來說應該是透明的。然而,有一些問題需要考慮。

  • final 類不能被代理,因為它們不能被繼承。
  • final 方法不能被adivce,因為它們不能被覆寫。
  • private 方法不能被adivce,因為它們不能被覆寫。
不需要将CGLIB添加到你的classpath中。CGLIB被重新打包并包含在 spring-core JAR中。換句話說,基于CGLIB的AOP "開箱即用",正如JDK動态代理一樣。

CGLIB代理和動态代理之間的性能差異很小。在這種情況下,性能不應該是一個決定性的考慮。

6.4.6. 使用 “全局” (Global)Advisor

通過在攔截器名稱上附加星号,所有bean名稱與星号前的部分相比對的 advisor 都會被添加到 advisor 鍊中。如果你需要添加一套标準的 "全局" advisor,這就很友善了。下面的例子定義了兩個全局 advisor。

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>           

6.5. 簡明的代理定義

特别是在定義事務代理時,你可能最終會有許多類似的代理定義。使用父bean和子bean的定義,以及内部bean的定義,可以使代理定義更幹淨、更簡潔。

首先,我們為代理建立一個父類、模闆、bean類定義,如下所示。

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>           

這本身永遠不會被執行個體化,是以它實際上可以是不完整的。然後,每個需要建立的代理都是一個子Bean定義,它将代理的目标包裹成一個内部Bean定義,因為無論如何目标都不會單獨使用。下面的例子展示了這樣一個子Bean。

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

你可以覆寫來自父模闆的屬性。在下面的例子中,我們覆寫了事務傳播的設定。

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>           

請注意,在父類Bean的例子中,我們通過将 abstract 屬性設定為 true,明确地将父類Bean的定義标記為抽象,如 前 所述,是以它實際上可能永遠不會被執行個體化。應用程式上下文(但不是簡單的Bean工廠),預設情況下,會預先執行個體化所有的singletons。是以,重要的是(至少對于單子Bean),如果你有一個(父)Bean定義,你打算隻作為模闆使用,并且這個定義指定了一個類,你必須確定将 abstract 屬性設定為 true。否則,應用程式上下文實際上會試圖預先将其實體化。

6.6. 用ProxyFactory以程式設計方式建立AOP代理

用Spring以程式設計方式建立AOP代理很容易。這讓你可以在不依賴Spring IoC的情況下使用Spring AOP。

目标對象實作的接口會自動被代理。下面的清單顯示了為一個目标對象建立一個代理,有一個攔截器和一個advisor。

Java

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

第一步是構造一個 org.springframework.aop.framework.ProxyFactory 類型的對象。你可以像前面的例子那樣用一個目标對象來建立它,或者在另一個構造函數中指定要代理的接口。

你可以添加advice(用攔截器作為一種專門的advice)、advisor或兩者,并在 ProxyFactory 的生命中操縱它們。如果你添加一個 IntroductionInterceptionAroundAdvisor,你可以使代理實作額外的接口。

在 ProxyFactory 上還有一些友善的方法(繼承自 AdvisedSupport),可以讓你添加其他的建議類型,比如 before 和 throws advice。AdvisedSupport 是 ProxyFactory 和 ProxyFactoryBean 的超類。

在大多數應用中,将AOP代理的建立與IoC架構相結合是最佳實踐。我們建議你用AOP将配置從Java代碼中外部化,一般來說,你應該這樣做。

6.7. 操作 advice 的對象

無論你如何建立AOP代理,你都可以通過使用 org.springframework.aop.framework.Advised 接口來操作它們。任何AOP代理都可以被強制轉換為個接口,不管它實作了哪些其他接口。這個接口包括以下方法。

Java

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();
           

getAdvisors() 方法為每個advisor、攔截器或其他已添加到工廠的advisor類型傳回一個 Advisor。如果你添加了一個 Advisor,在這個索引處傳回的advisor就是你添加的對象。如果你添加了一個攔截器或其他advice類型,Spring将其包裝在一個advisor中,并有一個總是傳回 true 值的指針。是以,如果你添加了一個 MethodInterceptor,這個索引傳回的顧問是一個 DefaultPointcutAdvisor,它傳回你的 MethodInterceptor 和一個比對所有類和方法的 pointcut。

addAdvisor() 方法可以用來添加任何 Advisor。通常,持有pointcut和advice的advisor是通用的 DefaultPointcutAdvisor,你可以将其用于任何advice或pointcut(但不用于introduction)。

預設情況下,即使代理被建立後,也可以添加或删除advisor或攔截器。唯一的限制是不可能添加或删除引入advisor,因為現有的來自工廠的代理不顯示接口的變化。(你可以從工廠獲得一個新的代理來避免這個問題)。

下面的例子顯示了将一個AOP代理強制轉換為 Advised 接口,并檢查和操作其advice。

Java

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 using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
           
在生産中修改業務對象的advice是否可取(無雙關之意)是值得懷疑的,盡管毫無疑問有合法的使用案例。然而,它在開發中可能非常有用(例如,在測試中)。我們有時發現,能夠以攔截器或其他advice的形式添加測試代碼,進入我們想要測試的方法調用中,是非常有用的。(例如,advice可以進入為該方法建立的事務中,也許是運作SQL來檢查資料庫是否被正确更新,然後再标記事務進行復原)。

根據你建立代理的方式,你通常可以設定一個 frozen 标志。在這種情況下,Advised isFrozen() 方法傳回 true,任何試圖通過添加或删除來修改 advice 的行為都會導緻 AopConfigException。freeze advice 對象的狀态的能力在某些情況下是有用的(例如,防止調用代碼删除安全攔截器)。

6.8. 使用 "auto-proxy" (自動代理)設施

到目前為止,我們已經考慮了通過使用 ProxyFactoryBean 或類似的工廠Bean顯式建立AOP代理。

Spring還讓我們使用 "自動代理" 的Bean定義,它可以自動代理標明的Bean定義。這是建立在Spring的 "Bean Post Processor" 基礎設施上的,它可以在容器加載時修改任何Bean定義。

在這種模式下,你在你的XML Bean定義檔案中設定了一些特殊的Bean定義來配置自動代理的基礎設施。這讓你可以聲明符合自動代理的目标。你不需要使用 ProxyFactoryBean。

有兩種方法可以做到這一點。

  • 通過使用一個自動代理建立者(auto-proxy creator),在目前上下文中引用特定的bean。
  • 值得單獨考慮的自動代理建立的一個特殊情況:由源級中繼資料屬性驅動的自動代理建立。

6.8.1. Auto-proxy Bean 定義

本節涉及 org.springframework.aop.framework.autoproxy 包提供的自動代理建立者。

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator 類是一個 BeanPostProcessor,它為名稱與字面值或通配符相比對的Bean自動建立AOP代理。下面的例子展示了如何建立一個 BeanNameAutoProxyCreator Bean。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>           

與 ProxyFactoryBean 一樣,有一個 interceptorNames 屬性,而不是一個 interceptors 的清單,以允許prototype advisor 的正确行為。被命名的 “interceptors” 可以是advisor或任何advice類型。

與一般的自動代理一樣,使用 BeanNameAutoProxyCreator 的主要意義在于将相同的配置一緻地應用于多個對象,而配置量最小。它是将聲明性事務應用于多個對象的一個流行選擇。

名字比對的Bean定義,比如前面例子中的 jdkMyBean 和 onlyJdk,都是目标類的普通Bean定義。一個AOP代理是由 BeanNameAutoProxyCreator 自動建立的。同樣的advice被應用于所有比對的Bean。請注意,如果使用advisor(而不是前面例子中的攔截器),那麼對不同的Bean适用的 pointcuts 可能不同。

DefaultAdvisorAutoProxyCreator

一個更普遍和極其強大的自動代理建立者是 DefaultAdvisorAutoProxyCreator。它可以自動應用目前上下文中符合條件的advisor,而不需要在自動代理advisor的Bean定義中包含特定的Bean名稱。它提供了與 BeanNameAutoProxyCreator 一樣的一緻配置和避免重複的優點。

使用這一機制涉及到:

  • 指定一個 DefaultAdvisorAutoProxyCreator bean 類定義。
  • 在相同或相關的 context 下指定任何數量的advisor。注意,這些必須是advisor,而不是攔截器或其他advice。這是必要的,因為必須有一個 pointcut 來評估,以檢查每個 advice 對候選bean定義的資格。

DefaultAdvisorAutoProxyCreator 會自動評估每個advisor中包含的 pointcut,以檢視它應該對每個業務對象(例如例子中的 businessObject1 和 businessObject2)應用什麼(如果有的話)advice。

這意味着任何數量的advisor可以被自動應用到每個業務對象。如果任何advisor中的任何點都不比對業務對象中的任何方法,那麼該對象就不會被代理。當Bean定義被添加到新的業務對象時,如果有必要,它們會被自動代理。

一般來說,自動代理的好處是使調用者或依賴者不可能獲得一個不被advice的對象。在這個 ApplicationContext 上調用 getBean("businessObject1") 會傳回一個 AOP 代理,而不是目标業務對象。(前面顯示的 “inner bean” 成語也提供了這個好處)。

下面的例子建立了一個 DefaultAdvisorAutoProxyCreator Bean和本節讨論的其他元素。

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

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

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

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

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

如果你想對許多業務對象一緻地應用相同的advice,DefaultAdvisorAutoProxyCreator 就非常有用。一旦基礎設施定義到位,你可以添加新的業務對象而不包括具體的代理配置。你也可以輕松地加入額外的切面(例如,跟蹤或性能監控切面),隻需對配置進行最小的改變。

DefaultAdvisorAutoProxyCreator 提供了對過濾(通過使用命名慣例,以便隻對某些顧問進行評估,這允許在同一工廠中使用多個不同配置的 AdvisorAutoProxyCreator)和排序的支援。顧問可以實作 org.springframework.core.Ordered 接口,以確定正确的排序,如果這是一個問題。前面例子中使用的 TransactionAttributeSourceAdvisor 有一個可配置的順序值。預設設定是無序的。

6.9. 使用TargetSource實作

Spring提供了 TargetSource 的概念,用 org.springframework.aop.TargetSource 接口表示。這個接口負責傳回實作連接配接點的 “target object”。每次AOP代理處理方法調用時,都會要求 TargetSource 實作提供一個目标執行個體。

使用Spring AOP的開發者通常不需要直接使用 TargetSource 實作,但這提供了支援池、熱插拔和其他複雜目标的強大手段。例如,通過使用一個池來管理執行個體,一個池化的 TargetSource 可以為每次調用傳回不同的目标執行個體。

如果你沒有指定 TargetSource,就會使用一個預設的實作來包裝一個本地對象。每次調用都會傳回同一個目标(正如你所期望的)。

本節的其餘部分描述了Spring提供的标準目标源(arget source)以及你如何使用它們。

當使用自定義目标源時,你的目标通常需要是一個prototype,而不是一個singleton bean定義。這允許Spring在需要時建立一個新的目标執行個體。

6.9.1. 可熱插拔的目标源

org.springframework.aop.target.HotSwappableTargetSource 的存在是為了讓AOP代理的目标被切換,同時讓調用者保留他們對它的引用。

改變目标源的目标會立即生效。HotSwappableTargetSource 是線程安全的。

你可以通過使用 HotSwappableTargetSource 的 swap() 方法來改變目标,如下面的例子所示。

Java

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

下面的例子顯示了所需的XML定義。

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

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

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

前面的 swap() 調用改變了可交換Bean的目标。持有對該Bean的引用的用戶端并不知道這一變化,而是立即開始命中新的目标。

雖然這個例子沒有添加任何advice(使用 TargetSource 不一定要添加advice),但任何 TargetSource 都可以與任意advice一起使用。

6.9.2. 池化目标源

使用池化目标源提供了一個類似于無狀态會話EJB的程式設計模型,在這個模型中,一個相同的執行個體池被維護,方法的調用會被送到池中的自由對象。

Spring池和SLSB池的一個重要差別是,Spring池可以應用于任何POJO。正如Spring一般,這種服務可以以非侵入性的方式應用。

Spring提供了對Commons Pool 2.2的支援,它提供了一個相當高效的池化實作。你需要在你的應用程式的classpath上安裝 commons-poo`l Jar來使用這個功能。你也可以子類化 `org.springframework.aop.target.AbstractPoolingTargetSource,以支援任何其他池化API。

Commons Pool 1.5+也被支援,但從Spring Framework 4.2開始被廢棄。

下面的清單顯示了一個配置的例子。

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

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

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

注意,目标對象(前面例子中的 businessObjectTarget)必須是一個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="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>           

這個advisor是通過調用 AbstractPoolingTargetSource 類上的一個友善方法獲得的,是以要使用 MethodInvokingFactoryBean。這個advisor的名字(這裡是 poolConfigAdvisor)必須在暴露池對象的 ProxyFactoryBean 中的攔截器名稱清單中。

cast 的定義如下。

Java

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
           
通常沒有必要對無狀态服務對象進行池化。我們認為這不應該是預設的選擇,因為大多數無狀态對象自然是線程安全的,而且如果資源被緩存,執行個體池就會出現問題。

通過使用自動代理,可以獲得更簡單的池。你可以設定任何自動代理建立者所使用的 TargetSource 實作。

6.9.3. Prototype 目标源

設定一個 “prototype” 目标源類似于設定一個池化的 TargetSource。在這種情況下,每個方法調用時都會建立一個新的目标執行個體。盡管在現代JVM中建立一個新對象的成本并不高,但為新對象裝配(滿足其IoC依賴性)的成本可能更高。是以,如果沒有很好的理由,你不應該使用這種方法。

要做到這一點,你可以将前面顯示的 poolTargetSource 定義修改如下(為了清楚起見,我們還改變了名稱)。

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

唯一的屬性是目标Bean的名字。繼承在 TargetSource 的實作中使用,以確定命名的一緻性。與池化目标源一樣,目标Bean必須是一個 prototype Bean定義。

6.9.4.ThreadLocal目标源

如果你需要為每個傳入的請求(即每個線程)建立一個對象,那麼 ThreadLocal 目标源就很有用。ThreadLocal 的概念提供了一個JDK範圍内的設施,可以透明地将資源與線程一起存儲。設定 ThreadLocalTargetSource 的方法與其他類型的目标源的方法基本相同,正如下面的例子所示。

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>           
當在多線程和多類加載器環境中不正确地使用 ThreadLocal 執行個體時,會帶來嚴重的問題(可能會導緻記憶體洩露)。你應該總是考慮将一個 ThreadLocal 包裝在其他類中,而不是直接使用 ThreadLocal 本身(除了在包裝類中)。此外,你應該始終記得正确地設定和取消設定(後者隻需要調用 ThreadLocal.set(null))線程的本地資源。在任何情況下都應該取消設定,因為不取消設定可能會導緻有問題的行為。Spring的 ThreadLocal 支援為你做到了這一點,在使用 ThreadLocal 執行個體時,應始終考慮到沒有其他适當的處理代碼。

6.10. 定義新的 Advice Type

Spring AOP被設計為可擴充的。雖然目前内部使用的是攔截實作政策,但除了around advice、before、throws advice 和 after returning advice 的攔截(interception)之外,還可以支援任意的 advice 類型。

org.springframework.aop.framework.adapter 包是一個SPI包,可以在不改變核心架構的情況下增加對新的自定義 advice 類型的支援。對自定義 Advice 類型的唯一限制是它必須實作 org.aopalliance.aop.Advice 标記接口。