1. 程式人生 > >《Spring 5官方文件》37. Spring AOP的經典用法

《Spring 5官方文件》37. Spring AOP的經典用法

原文連結

在本附錄中,我們會討論一些初級的Spring AOP介面,以及在Spring 1.2應用中所使用的AOP支援。
對於新的應用,我們推薦使用 Spring AOP 2.0來支援,在AOP章節有介紹。但在已有的專案中,或者閱讀資料或者文章時,可能會遇到Spring AOP 1.2風格的示例。Spring 2.0完全相容Spring 1.2,在本附錄中所有的描述都是Spring 2.0所支援的。

Spring中的切入點API

一起看一下Spring是如何處理關鍵切入點這個概念。

概念

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代理建立時執行,避免需要在每一個方法呼叫時,再測試一次。
如果對於一個給定的方法,matches(Method, Class)方法返回true,並且對於同MethodMatcher例項的isRuntime()方法也返回true,
那麼在每次被匹配的方法執行時,都會呼叫boolean matches(Method m, Class targetClass, Object[] args)方法。
這樣使得在目標增強執行前,一個切點可以在方法執行時立即檢視入參。

大部分MethodMatcher是靜態的,意味著他們isRuntime()方法的返回值是false。
在這種情況下,boolean matches(Method m, Class targetClass, Object[] args)方法是永遠不會被呼叫的。

提示

如果可以,儘量將切點設定為靜態,這樣在一個AOP代理生成後,可以允許AOP框架快取評估的結果。

切點操作

Spring在切點的操作:尤其是,組合(union)交叉(intersection)

  • 組合意味著方法只需被其中任意切點匹配。
  • 交叉意味著方法需要被所有切點匹配。
  • 組合通常更為有用。
  • 切點可以使用org.springframework.aop.support.Pointcuts類或者org.springframework.aop.support.ComposablePointcut中的靜態方法組合。
    然而,使用AspectJ的切點表示式通常是一種更為簡單的方式。

AspectJ切點表示式

自從2.0版以後,Spring所使用的最重要切點型別就是org.springframework.aop.aspectj.AspectJExpressionPointcut
這個切點使用了一個AspectJ支援的庫,用以解析AspectJ切點表示式的字串。

有關原始AspectJ切點元素支援的討論,請參閱之前章節。

方便的切點實現

Spring提供了幾個方便的切點具體實現。有些可以在框架外使用;其他的則為應用程式的特定切點實現所需要的子類。

靜態切點

靜態切點是基於方法和目標類的,不能將方法引數也考慮其中。
對於大多數用法,靜態切點是足夠且最佳的選擇。

對於Spring來說,當一個方法第一次被呼叫是,對靜態切點僅僅評估一次是可行的:在本次評估後,再次呼叫該方法時,就沒有必要再對切點進行評估。

我們一起看一些Spring中包含的靜態切點具體實現。

正則表示式切點

一個顯而易見的方式是使用正則表示式來指定靜態切點。幾個在Spring之外的框架可以實現這部分功能。
org.springframework.aop.support.Perl5RegexpMethodPointcut是一個常見的正則表示式切點,使用Perl 5正則表示式語法。
Perl5RegexpMethodPointcut類的正則表示式匹配依賴於Jakarta ORO。
Spring也提供了JdkRegexpMethodPointcut類,可以在JDK 1.4版本之上使用正則表示式。

使用Perl5RegexpMethodPointcut類,你可以提供一個正則表示式字串的列表。
如果與該列表的中的某個正則匹配上了,那麼切點的判定就為true(判定的結果是這些切點的有效組合)。

使用方法如下所示:

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

Spring提供了一個方便的類,RegexpMethodPointcutAdvisor,允許我們引用一個Advice(記住一個Advice可能是一個介入增強、前置增強、或者異常丟擲增強等)。
實際上,Spring會使用JdkRegexpMethodPointcut類。
使用RegexpMethodPointcutAdvisor簡化配置,這個類封裝了切點和增強。如下所示:

<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可以被任意型別的增強使用。

屬性驅動切入

一個重要的靜態切點就是metadata-driven切點。它會使用一些元資料屬性資訊:通常是原始碼級的元資料。

動態切點

動態切點的判定代價比靜態切點要大。動態切點除了靜態資訊外,還需要考慮方法引數
這意味著它們在每次方法呼叫時都必須進行判定;判定的結果不能被快取,因為引數是變化的。

代表性的事例是控制流切點。

控制流切點

Spring的控制流切點在概念上與AspectJ的cflow切點類似,不過功能稍弱。(目前沒有方法,可以指定一個切點在其他切點匹配的連線點後執行。)
一個控制流切點匹配當前的呼叫棧【待定】。例如,如果一個連線點被一個在com.mycompany.web包中、或者SomeCaller類中的方法呼叫,就會觸發。
控制流切點使用org.springframework.aop.support.ControlFlowPointcut類來指定。
說明

控制流切點在執行時進行評估明顯代價更大,甚至是其他動態切點。在Java 1.4,大概是其他動態切點的5倍。

Pointcut父類

Spring提供了一些有用的切點父類,方便開發者實現自己的切點。

因為靜態切點是最為實用的,你可能需要實現StaticMethodMatcherPointcut的子類,如下所示。
這裡只需要實現一個抽象方法即可(雖然也可以覆蓋其他方法來自定義類的行為)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

}

Spring也有動態切點的父類。
在Spring 1.0 RC2版本之後,可以自定義任意增強型別的切點。

自定義切點

由於切點在Spring AOP中都是Java類,而不是語言特徵(就像在AspectJ中),可以宣告自定義切點,無論靜態還是動態。
自定義切點在Spring中是可以任意複雜的。然而,如果可以,推薦使用AspectJ切點表示式。

說明

Spring之後的版本可能支援由JAC提供的“語義切點”。
例如:在目標物件中,所有修改例項變數的方法。

Spring中的Advice介面

現在讓我們看一下Spring AOP如何處理Advice(增強)。

Advice的生命週期

每個Advice都是一個Spring的Bean。一個Advice例項在被增強的物件間共享,或者對於每一個被增強的物件都是唯一的。
這取決於增強是類級的、還是物件級的【待定】。
Each advice is a Spring bean. An advice instance can be shared across all advised
objects, or unique to each advised object. This corresponds to per-class or
per-instance advice.

Per-class級增強最為常用。它適用於通常的增強,例如事務增強。這種增強不依賴於代理物件或者增加新的狀態;它們只是對方法和引數進行增強。
Per-instance級增強適用於介紹,支援它很複雜【待定】。在本示例中,增強對被代理的物件添加了一個狀態。

也可以在同一個AOP代理中,使用共享和per-instance級增強的組合。

Spring中的增強型別

Spring在框架層之外,支援多種方式的增強,並且支援任意增強型別的可擴充套件性。
我們一起了解一下標準增強型別和增強的基礎概念。

攔截式環繞型增強

Spring中最基本的增強型別之一就是攔截式環繞型增強
通過使用方法攔截器,Spring完全符合AOP聯盟的環繞型增強介面。
環繞型方法攔截器應該實現以下介面:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke() 方法的MethodInvocation表示了將要被呼叫的方法、目標連線點、AOP代理、以及該方法的引數。
invoke() 方法應當返回呼叫結果:目標連線點的返回值。

一個簡單的方法攔截器實現如下所示:

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }

}

注意呼叫MethodInvocation物件的proceed()方法。這個方法將攔截器鏈路調向連線點。
大多數攔截器會呼叫該方法,並返回該方法的值。但是,就像任意環繞增強一樣,一個方法攔截器也可以返回一個不同的值,或者丟擲一個異常,而不是呼叫proceed()方法。
但是,沒有足夠的理由,不要這麼幹!

說明

方法攔截器提供與其他AOP聯盟標準的AOP實現的互通性。
在本章剩餘部分討論的其他型別增強,會以Spring特定的方式實現AOP的概念。
使用最為具體的型別增強有一定優勢,但如果你想在其他AOP框架中使用切面,就需要堅持使用方法攔截器。
需要注意的是,切點在框架間是不通用的,AOP聯盟目前沒有定義切點的介面。

前置增強

一個簡單的增強型別是前置增強。這種增強不需要一個MethodInvocation物件,因為它僅僅在方法進入時被呼叫。

前置增強的優勢是不需要呼叫proceed()方法,因此不會無故中斷呼叫鏈。

MethodBeforeAdvice介面如下所示。【待定】
interface is shown below. (Spring’s API design would allow for
field before advice, although the usual objects apply to field interception and it’s
unlikely that Spring will ever implement it).

public interface MethodBeforeAdvice extends BeforeAdvice {

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

}

需要注意的是該方法的返回型別是void。前置增強可以在連線點錢插入一些自定義的行為,但是不能改變返回結果。
如果一個前置增強丟擲一個異常,它會中斷呼叫鏈中接下來的執行步驟。這個異常將傳遞到呼叫鏈的上一層。
如果該異常沒有被處理,或者在被呼叫方法中籤名【待定】,這個異常會直接傳遞給方法呼叫方;否則,該異常會被AOP代理類封裝到一個未經檢查的異常中。

在Spring中,一個前置增強的例子:統計所有方法的執行次數:

public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

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

    public int getCount() {
        return count;
    }
}

提示

前置增強可以被任何切點使用。

異常丟擲增強

當連線點返回的結果是一個丟擲的異常時,異常丟擲增強會被呼叫。
Spring提供異常丟擲增強。
需要主意的是org.springframework.aop.ThrowsAdvice 介面不包括任何方法:它是一個標籤式介面,標識給出的物件實現了一個或多個型別的異常丟擲增強。
它們的格式如下所示:

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

只有最後一個引數是必須的。這個方法可能擁有1個或者4個引數,取決於增強方法是否對被增強的方法和方法引數感興趣。
下面的類是異常丟擲增強的例子。

如果一個RemoteException(包括子類)被丟擲,下面這個增強就會被呼叫:


public class RemoteThrowsAdvice implements ThrowsAdvice {

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

}

如果一個ServletException被丟擲,下面這個增強就會被呼叫。
與上面不同的是,該方法聲明瞭4個引數,因此它可以訪問被呼叫的方法、方法引數和目標物件:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

}

最後一個示例描述了,一個類中如果宣告兩個方法,可以同時處理RemoteExceptionServletException
一個類中可以包含任意個異常丟擲增強的處理方法。

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

說明

如果一個異常丟擲增強本身丟擲了一個異常,它將覆蓋掉原始的異常(例如,改變拋給使用者的異常)。
這個覆蓋的異常通常是一個執行時異常;這樣就可以相容任何的方法簽名。
但是,如果一個異常丟擲增強丟擲了一個檢查時異常,這個異常必須和該目標方法的宣告匹配,以此在一定程度上與特定的目標籤名相結合。

不要丟擲與目標方法簽名不相容的檢查時異常!

提示

異常丟擲增強可以被任意切點使用。

後置增強

後置增強必須實現org.springframework.aop.AfterReturningAdvice介面,如下所示:

public interface AfterReturningAdvice extends Advice {

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

}

一個後置增強可以訪問被呼叫方法的返回值(不能修改)、被呼叫方法、方法引數、目標物件。

下面的後置增強統計了所有執行成功的方法呼叫,即沒有丟擲異常的呼叫:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

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

    public int getCount() {
        return count;
    }

}

這個增強不會改變執行路徑。如果它丟擲了一個異常,該異常會丟擲到攔截鏈,而不是返回返回值。

提示

後置增強可以被任意切點使用。

引介增強

Spring將引介增強當作一個特殊的攔截式增強。

引介增強需要一個IntroductionAdvisor和一個IntroductionInterceptor實現以下介面:

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);

}

invoke()方法繼承自AOP聯盟的MethodInterceptor介面,必須被引介實現:
也就是說,如果被呼叫的方式是一個被介入的介面,該引介攔截器就會負責處理該方法的呼叫,不能呼叫proceed()方法。

不是所有的切點都可以使用引介增強,因為它只適用於類級,而不是方法級。
你可以通過IntroductionAdvisor來使用引介增強,該類有如下幾個方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;

}

public interface IntroductionInfo {

    Class[] getInterfaces();

}

沒有MethodMatcher,因此也沒有Pointcut與引介增強相關聯。只有類過濾器是符合邏輯的。
getInterfaces()方法會返回被該增強器引介的介面集合。
validateInterfaces()會在內部被呼叫,用於確定被引介的介面是否可以被配置的IntroductionInterceptor所實現。

讓我們一起看一個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被設計成代理一個需要被引介介面的真實實現,隱藏使用攔截器去這樣做。
使用建構函式的引數,可以把代理設定為任意物件;預設的代理(使用無參建構函式時)就是引介增強【待定】。
The delegate can be set to any object using a constructor argument; the
default delegate (when the no-arg constructor is used) is this.
因此在下面的示例中,代理是DelegatingIntroductionInterceptor的子類LockMixin
給定的代理(預設是自身),一個DelegatingIntroductionInterceptor物件查詢所有被該代理所實現的介面結合(除了IntroductionInterceptor),
並支援代理介入它們。
LockMixin的子類呼叫suppressInterface(Class intf)方法,可以禁止不能被暴露的介面被呼叫。
然而無論一個IntroductionInterceptor準備支援多少個介面,IntroductionAdvisor都會控制哪些介面實際是被暴露的。
一個被引介的介面會隱藏掉目標物件的所有介面的實現。

因此DelegatingIntroductionInterceptor的子類LockMixin,也實現了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代理會呼叫方法,否則呼叫連線點,通常也是足夠了。
在這種情況下,我們需要加入一個檢查:如果處於鎖住的模式,任何setter方法都是不能被呼叫。

所需要的引介增強器非常簡單。它所需要做的僅僅是持有一個明確的LockMixin物件,指定需要被引介的介面(在本示例中,僅僅是Lockable介面)。
一個更加複雜的例子是持有一個引介攔截器的引用(被定義為一個原型):在本示例中,沒有配置和一個LockMixin物件相關,所有我們簡單使用new來建立。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

}

我們可以非常簡單的使用這個增強器:不需要任何配置。(但是,使用IntroductionInterceptor的同時,不使用IntroductionAdvisor是不行的。)
和之前介紹的一樣,Advisor是一個per-instance級的,它是有狀態的。
因此對於每一個被增強的物件,就像LockMixin 一樣,我們都需要一個不同的LockMixinAdvisor
Advisor就是被增強物件狀態的一部分。

我們可以使用程式設計的方式應用這個Advisor,使用Advised.addAdvisor()方法,或者在XML中配置(推薦),就像其他Advisor一樣。
下面會討論所有代理建立的選擇方式,包括“自動代理建立者”正確的處理引介和有狀態的mixins。

Spring中的Advisor介面

在Spring中,一個Advisor是一個切面,僅僅包括了一個和切點表示式相關聯的增強物件。

除了介紹的特殊情況,任何Advisor都可以被任意增強使用。
org.springframework.aop.support.DefaultPointcutAdvisor 是最為常用的advisor類。
例如,它可以被MethodInterceptorBeforeAdviceThrowsAdvice使用。

在Spring的同一個AOP代理中,有可能會混淆Advisor和增強。
例如,在一個代理的配置中,你可能使用了一個攔截式環繞增強、異常丟擲增強和前置增強:Spring會自動建立需要的攔截鏈。

使用ProxyFactoryBean建立AOP代理

如果你的業務物件使用了Spring IoC容器(一個ApplicationContext或者BeanFactory),你應該、也會希望使用一個Spring的AOP FactoryBean。
(需要注意的是,一個FactoryBean間接的引入了一層,該層可以建立不同型別的物件。)

說明

Spring 2.0 AOP在內部也是用了工廠物件。

在Spring中,建立AOP代理最基礎的方式是使用org.springframework.aop.framework.ProxyFactoryBean類。
這樣可以完全控制將要使用的切點和增強,以及它們的順序。
然而,更簡單的是這是可選的,如果你不需要這樣的控制。

基礎

ProxyFactoryBean就像Spring其他FactoryBean的實現一樣,間接的引入了一個層次。
如果你定義了一個名為fooProxyFactoryBean,那麼物件引用的foo,不是ProxyFactoryBean例項本身,
而是ProxyFactoryBean 物件呼叫getObject()方法的返回值。
這個方法會建立一個AOP代理來包裝目標物件。

使用一個ProxyFactoryBean或者IoC感知類來建立AOP代理的最大好處之一是,增強和切點同樣也可以被IoC管理。
這是一個強大的功能,實現的方法是其他AOP框架難以企及的。

JavaBean屬性

與大多數Spring所提供的FactoryBean實現相同的是,ProxyFactoryBean 本身也是一個JavaBean。
它的屬性用於:

一些關鍵的屬性繼承自org.springframework.aop.framework.ProxyConfig(Spring中所有代理工廠的超類)
這些關鍵屬性包括:

  • proxyTargetClass: 如果目標類將被代理標誌為true,而不是目標類的介面。
    如果該屬性設定為true,CGLIB代理就會被建立(但也需要參見基於JDK和CGLIB的代理
  • optimize: 控制是否積極優化通過CGLIB建立的代理類。除非完全瞭解AOP代理相關的優化處理,否則不要使用這個設定。
    這個設定當前只對CGLIB代理有效;對JDK動態代理無效。
  • frozen: 如果一個代理配置是frozen,那麼就不再允許對該配置進行更改。
    如果你不想呼叫者在代理建立後操作該代理(通過被增強的介面),作為輕微的優化手段是該配置是很有用的。
    該配置的預設值是false,因此增加附帶的advice是允許的。
  • exposeProxy: 該屬性決定當前的代理是否在暴露在ThreadLocal中,讓它可以被目標物件訪問到。
    如果一個目標物件需要獲取該代理,exposeProxy就設定為true,目標物件可以通過AopContext.currentProxy()方法獲取當然的代理。
  • aopProxyFactory: 需要使用的AopProxyFactory實現。提供了是否使用動態代理的自定義方式,CGLIB或者其他代理模式。
    該屬性的預設是適當的選擇動態代理或者CGLIB。沒有必要使用該屬性;在Spring 1.1中它的目的在於新增新的代理型別。

ProxyFactoryBean的其他屬性:

  • proxyInterfaces: 介面名稱的字串陣列。如果沒有提供該屬性,會使用一個CGLIB代理參見(基於JDK和CGLIB的代理
  • interceptorNames: Advisor字串陣列,需要使用的攔截器或者其他advice的名稱。
    順序非常重要,先到的先處理。也就是列表中的第一個攔截器將會第一個處理呼叫。

這些名稱是當前工廠的例項名稱,包括從祖先工廠繼承來的名稱。
這裡不能包括bean的引用,因為這麼做的結果是ProxyFactoryBean忽略advice的單例設定。

你可以在一個攔截器名稱後新增一個星號( *)。這樣在應用中,所有以型號前的部分為名稱開始的advisor物件,都將被應用。
這個特性的示例可以在使用’全域性’advisor中找到。

  • singleton: 是否該工廠返回一個單例物件,無論呼叫多少次getObject()方法。
    某些FactoryBean實現提供了這樣的方法。該配置的預設值是true
    如果你需要使用一個有狀態的advice,例如有狀態的mixins,使用prototype的advice,以及將該屬性設定為false

基於JDK和CGLIB的代理

本章作為明確的文件,介紹ProxyFactoryBean對於一個特定的目標物件(即被代理的物件)如何選擇建立一個基於JDK的還是基於CGLIB的代理。

說明

ProxyFactoryBean建立基於JDK或基於CGLIB的代理在Spring 1.2.x和2.0版本間有所改變。
ProxyFactoryBean目前與TransactionProxyFactoryBean類的自動檢測介面所表現的語義相似。

如果被代理的目標物件的類(以下簡稱目標類)沒有實現任何介面,那麼就會建立基於CGLIB的代理。
這是一個最簡單的情景,因為JDK代理是基於介面的,沒有介面就意味著JDK代理類是行不通的。
即一個簡單的目標類插入,通過interceptorNames屬性指定一系列的攔截器。
需要注意的是即使ProxyFactoryBeanproxyTargetClass屬性被設定為false,也會建立基於CGLIB的代理。
(這顯然沒有任何意義,而且最好從Bean定義中移除,因為它是冗餘的,而且是很糟的混淆。)

如果目標類實現了一個(或者多個)介面,那麼被建立代理的型別取決於ProxyFactoryBean的配置。
如果ProxyFactoryBeanproxyTargetClass屬性被置為true,那麼會建立基於CGLIB的代理。
這很有道理,並且符合最小驚訝原則。
即使ProxyFactoryBeanproxyInterfaces屬性被設定成一個或多個全量的介面名稱,只要proxyTargetClass屬性被置為true,就會建立基於CGLIB的代理。

即使ProxyFactoryBeanproxyInterfaces屬性被設定成一個或多個全量的介面名稱,那麼就會建立基於JDK的代理。
被建立的代理會實現所有proxyInterfaces所指定的介面;如果目標類也實現的介面多餘proxyInterfaces所指定的,這也是可以的,但這些額外的介面不會被建立的代理所實現。

如果ProxyFactoryBeanproxyInterfaces沒有被設定,但是目標類也沒有實現一個(或多個)介面,
ProxyFactoryBean會自動檢測至少一個目標類實際實現的介面,並且建立一個基於JDK的代理。
實際上被代理的介面,就是目標類所有實現的介面;事實上,這和簡單的將目標類實現的每一個介面所組成的列表設定為proxyInterfaces屬性,效果是一樣的。
然而,自動檢測顯然減少了工作量,也不容易出現拼寫錯誤。

代理介面

我們一起看一個簡單的ProxyFactoryBean示例。這個例子涉及:

  • 一個將被代理的目標物件。在下面的示例中定義的是”personTarget”物件。
  • 一個Advisor和一個Interceptor用以提供增強。
  • 一個AOP代理物件指定了目標物件(”personTarget”物件)和需要代理的介面,以及應用的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.DebugInterceptor">
</bean>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
    <property name="target"><ref bean="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

需要主意的是interceptorNames屬性使用的是一個字串列表:當前工廠的interceptor或者advisor名稱。
Advisor、攔截器、前置增強、後置增強、異常丟擲增強都可以被使用。Advisor的排序很重要。

說明

你可能會疑惑,為什麼列表沒有持有bean的引用。
原因是如果一個ProxyFactoryBean的singleton屬性是false,它就必須返回一個獨立的代理物件。
如果每一個advisor物件本身是一個prototype的,就應該返回一個獨立的物件,因此從工廠中獲得一個prototype的例項是有必要的;持有一個引用是不行的。

上面定義的”person”物件可以被一個Person實現所替代,如下所示:

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的實現。
然而,它的class是一個動態代理類。它也可以被強制轉換為Advised介面(接下來會討論)。

可以使用內部匿名bean來隱藏目標和代理的區別。
只有ProxyFactoryBean的定義是不一樣的;包含的advice僅僅是為了示例的完整性:

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

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

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

這樣做有一個好處是隻會有一個Person型別的物件:如果我們想要阻止使用者從應用上下文中獲取一個沒有被advise的物件是很有用的,
或者需要阻止Spring IoC容器的自動注入時的歧義。
還有一個可以作為優點的是,ProxyFactoryBean定義是獨立的。
但是,有時候從工廠中可以得到一個沒有被advise的目標也是一個優點:比如在特定的測試場景。

代理類

如果你需要代理一個類,而不是代理一個或多個介面?

設想一下,在上面的例項中,如果沒有Person介面,我們需要去增強一個叫Person的類,該類沒有實現任何業務介面。
在這種情況下,你需要配置Spring,使用CGLIB代理,而不是動態代理。
只需要將ProxyFactoryBean的proxyTargetClass屬性置為true。
雖然最好使用介面變成,而不是類,但當增強遺留的程式碼時,增強目標類而不是目標介面,可能更為有用。
(通常情況下,Spring不是約定俗成的。它對應用好的實踐非常簡單,並且避免強制使用特定的實踐方式)

如果需要,你可以在任何情況下強制使用CGLIB,甚至對於介面。

CGLIB代理的的工作原理是在執行時生成目標類的子類。
Sprig將原始目標物件的方法呼叫委託給該生成的子類:該子類使用了裝飾器模式,在增強時織入。

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

  • Final方法是不能被advise的,因為它們不能被重寫。
  • 從Spring 3.2之後,就不再需要在專案的classpath中加入CGLIB的庫,CGLIB相關的類已經被重新打包在org.springframework包下,直接包含在prig-core 的jar包中。
    這樣即方便使用,又不會和其他專案所依賴的CGLIB出現版本衝突。

CGLIB代理和動態代理在效能上有所差異。
自從Spring 1.0後,動態代理稍快一些。
然而,這種差異在未來可能有所改變。
在這種情況下,效能不再是考慮的關鍵因素。

使用全域性advisor

通過在攔截器的名稱上新增星號,所有匹配星號前部分的名稱的advisor,都將新增到advisor鏈路中。
如果你需要新增一個套標準的全域性advisor,這可能會派上用場。0

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

簡明的代理定義

特別是在定義事務代理時,最終可能有許多類似的代理定義。
使用父、子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 id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

當然也可以覆蓋父模板的屬性,例如在本示例中,事務傳播的設定:

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

需要主意的是,在上面的示例中,我們通過abstract屬性明確的將父bean標記為抽象定義,
就如前面介紹的子bean定義,因此該父bean永遠不會被例項化。
應用上下文(不是簡單的bean工廠)預設會預先例項化所有單例。
因此,重要的是,如果你有一個僅僅想作為模板的bean(父bean)定義,並且指定了該bean的class,
那麼你必須保證該bean的abstract屬性被置為tue,否則應用上下文會嘗試在實際中預先例項化該bean。

使用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物件。
像上面的示例一樣,可以使用一個目標物件建立它,或者使用指定介面集的建構函式替代來建立該ProxyFactory。

你可以新增攔截器和advisor,並在ProxyFactory的生命週期中操作它們。
如果你新增一個IntroductionInterceptionAroundAdvisor,可以使得該代理實現附加的介面集合。

在ProxyFactory也有一些好用的方法(繼承自AdvisedSupport),允許你天機其他的增強型別,比如前置增強和異常丟擲增強。
AdvisedSupport是ProxyFactory和ProxyFactoryBean的超類。

提示

在IoC框架中整合AOP代理的建立在大多數應用中是最佳實踐。
通常,我們推薦在Java程式碼之外配置AOP。

操作被增強的物件

當你建立了AOP代理,你就能使用org.springframework.aop.framework.Advised介面來操作他們。
任何一個AOP代理,都能強制轉換成該介面,或者無論任何該代理實現的介面。
這個介面包含以下方法:

Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

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

void addAdvisor(Advisor advisor) throws AopConfigException;

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

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

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

boolean isFrozen();

getAdvisors()方法會返回新增到該工廠的每一個advisor、攔截器或者其它型別的增強。
如果你添加了一個Advisor,那麼返回Advisor陣列在該索引下的物件,就是你新增的那個。
如果你新增的是一個攔截器或者其他型別的增強,Spring將會把它包裝成一個帶有切點(切點判斷恆為真)的Advisor。
如果你添加了MethodInterceptor物件,該advisor getAdvisors()方法返回值,該索引處會是一個DefaultPointcutAdvisor物件,
該物件包括了你新增的MethodInterceptor物件和一個匹配所有類和方法的切點。

addAdvisor()可以用於新增任何Advisor。
通常該advisor是一個普通的DefaultPointcutAdvisor物件,包括了切點和advice,可以和任何advice或切點一起使用(除了引介增強)。

預設情況下,在一個代理被建立後,也可以新增或者刪除advisor和攔截器。
唯一的限制是,不能增加或者刪除一個引介advisor,因為已經從工廠生成的代理不能再進行介面修改。
(你可以從工廠中重新獲取一個新的代理來避免該問題)。

一個簡單的例子是強制轉換一個AOP代理成為Advised 物件,並且檢驗和操作它的advice:

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標誌,在這種情況下, AdvisedisFrozen()方法會返回true,
並且任何通過新增或者刪除方法試圖修改advice都會丟擲一個AopConfigException異常。
在一些情況下,凍結一個advise物件的狀態是有用的,例如,阻止呼叫程式碼刪除安全攔截器。
在Spring 1.1也用於積極優化,當執行時的修改被認為是沒必要的。

使用“autoproxy”能力

至此我們已經考慮過使用一個ProxyFactoryBean或者相似的工廠類建立明確的AOP代理。

Spring允許我們使用“autoproxy”bean定義,可以自動代理選擇的bean定義。
這是建立在Spring“bean後處理器(BeanPostProcessor)”機制之上,這可以允許在容器加在後修改任何bean定義。

在這個模型上,你可以在bean定義的XML檔案中設定一些特殊的bean定義,用以配置自動代理機制。
這允許你只需要宣告符合代理條件的目標即可:你不需要使用 ProxyFactoryBean

有兩種方式實現:

  • 在當前上下文中,使用一個指定了目標bean定義的自動代理建立器,
  • 一些特殊自動代理建立器需要分開考慮;由原始碼級元資料資訊驅動的自動代理建立器。

自動代理Bean的定義

org.springframework.aop.framework.autoproxy包提供了以下標準自動代理建立器

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator類是一個BeanPostProcessor,為純文字或者萬用字元匹配出的命名為目標bean自動建立AOP代理。

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

ProxyFactoryBean一樣,有一個interceptorNames屬性,而不是一個列表攔截器,
確保原型advisor正確的訪問方式。
命名為 “interceptors”,可以使任何advisor或者任何型別的advice。

和通常的自動代理一樣,使用BeanNameAutoProxyCreator的要領是,使用最小的配置量,將相同的配置一致地應用到多個物件上。

與bean定義匹配的命名,比如上面示例中的”jdkMyBean”和”onlyJdk”,就是目標類普通的原有bean定義。
一個AOP代理會被BeanNameAutoProxyCreator自動建立。相同的advice會被應用到所有匹配的bean上。
需要注意的是,被應用的advisor(不是上面示例中的攔截器),對不同的bean可能使用不同的切點。

DefaultAdvisorAutoProxyCreator

一個更一般且更強大的自動代理建立器是DefaultAdvisorAutoProxyCreator
在上下文中會自動應用符合條件的advisor,不需要在自動代理建立器的bean定義中指定目標物件的bean名稱。
它也提供了相同的有點,一致的配置和避免重複定義BeanNameAutoProxyCreator

使用此機制涉及:

  • 指定一個DefaultAdvisorAutoProxyCreator bean定義。
  • 在相同或者相關的上下文中指定一系列的Advisor。需要注意的是,這些都必須是Advisor,而不僅僅是攔截器或者其他的advice。
    這很必要,因為這裡必須有評估的切點,以便檢測候選bean是否符合每一個advice。

DefaultAdvisorAutoProxyCreator會自動的評估包含在每一個advisor中的切點,用以確定每一個業務物件需要應用的advice(就像示例中的 “businessObject1”和”businessObject2”)。

這意味著任意數量的advisor都能自動的應用到每一個業務物件。
如果所有在advisor的切點都不能匹配一個業務物件中的任何方法,這個物件就不會被代理。
由於bean的定義都是新增給建立的業務物件。如果需要,它們都會被自動代理。

通常情況下,自動代理都有使呼叫方或者依賴方不能獲取未被advise物件的優點。

在本ApplicationContext中呼叫getBean(“businessObject1”)會返回一個AOP代理,而不是目標業務物件(之前的“內部bean”也提供了這種優點)。

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

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

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

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

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

如果你想對許多業務物件應用相同的advice,DefaultAdvisorAutoProxyCreator將會有所幫助。
一旦基礎定義設定完成,你就可以簡單新增新的業務物件,不需要特定的proxy配置。
你也可以輕鬆地新增其他切面。例如,使用最小的配置修改,新增跟蹤或效能監控切面。

DefaultAdvisorAutoProxyCreator提供過濾(使用命名約定,以便只有特定的advisor被評估,允許在相同的工廠中使用多個、不同的被配置的AdvisorAutoProxyCreator)和排序的支援。
Advisor可以實現org.springframework.core.Ordered介面,當順序是一個問題時,確保正確的順序。
在上面示例中使用的TransactionAttributeSourceAdvisor,有一個可配置的順序值;預設配置是無序的。

AbstractAdvisorAutoProxyCreator

AbstractAdvisorAutoProxyCreator是DefaultAdvisorAutoProxyCreator的超類。
你可以通過繼承這個類建立自己的自動代理建立器,
雖然這種情況微乎其微,advisor定義為DefaultAdvisorAutoProxyCreator框架的行為提供了有限的定製。

使用元資料驅動

一個特別重要的自動代理型別就是元資料驅動。這和 .NET的ServicedComponents程式設計模型類似。
事務管理和其他企業服務的配置在原始碼屬性中儲存,而不是像在EJB中一樣使用XML部署描述符。

在這種情況下,你結合能夠解讀元資料屬性的Advisor,使用DefaultAdvisorAutoProxyCreator
元資料細節存放在備選advisor的切點部分,而不是自動建立器類的本身中。

這實際上是DefaultAdvisorAutoProxyCreator的一種特殊情況,但值得考慮(元資料感知程式碼在advisor切點中,而不是AOP框架自身上)。

JPetStore示例應用程式的/attributes目錄,展示了屬性驅動的使用方法。
在這種情況下,沒必要使用TransactionProxyFactoryBean
簡單在業務物件上定義事務屬性就足夠了,因為使用的是元資料感知切點。
包含了下面程式碼的bean定義,在 /WEB-INF/declarativeServices.xml檔案中。
需要注意的是這是通用的,也可以在JPetStore之外使用。

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

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

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
            <property name="attributes" ref="attributes"/>
        </bean>
    </property>
</bean>

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

DefaultAdvisorAutoProxyCreatorbean(命名不是重點,甚至可以省略)定義會獲取所有在當前應用上下文中符合的切點。
在這種情況下,TransactionAttributeSourceAdvisor類星的”transactionAdvisor” bean定義,將適用於攜帶了事務屬性的類或者方法。
TransactionAttributeSourceAdvisor通過建構函式依賴一個TransactionInterceptor物件。
本示例中通過自動裝配解決該問題。
AttributesTransactionAttributeSource依賴一個org.springframework.metadata.Attributes介面的實現。
在本程式碼片段中,”attributes” bean滿足這一點,使用Jakart aCommons Attributes API來獲取屬性資訊。
(這個應用程式碼必須使用Commons Attributes編譯任務編譯)

JPetStore示例應用程式的/annotation目錄包含了一個類似自動代理的示例,需要JDK 1.5版本以上的註解支援。
下面的配置可以自動檢測Spring的Transactional,為包含該註解的bean配置一個隱含的代理。

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

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

<bean id="transactionInterceptor"
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
    </property>
</bean>

這裡定義的 TransactionInterceptor依賴一個PlatformTransactionManager定義,沒有被包含在這個通用檔案中(儘管可以包含),
因為它對應用的事務需求是定製的(通常是JTA,就像本示例,或者是Hibernate、JDBC):

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

提示

如果你只需要宣告式事務管理,使用這些通用的XML定義會導致Spring為所有包含事務屬性的類或方法建立自動代理。
你不需要直接使用AOP,以及.NET和ServicedComponents相似的程式設計模型。

這種機制是可擴充套件的,可以基於通用屬性自動代理。你需要:

  • 定義你自己的個性化屬性。
  • 指定包含必要advice的Advisor,包括一個切點,該切點會被一個類或方法上存在的定義屬性所觸發。
    你也可以使用一個已有的advice,僅僅實現了獲取自定義屬性的一個靜態切點。

對每個被advise的類,這樣的advisor都可能是唯一的(例如mixins【待定】):
它們的bean需要被定義為prototype,而不是單例。
例如,Spring測試套件中的LockMixin引介攔截器,可以對一個mixin目標與一個屬性驅動切點一起使用。
我們使用通用的DefaultPointcutAdvisor,使用JavaBean屬性進行配置。

<bean id="lockMixin" class="org.springframework.aop.LockMixin"
        scope="prototype"/>

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

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

如果該屬性感知切點匹配了anyBean或者其他bean定義的任何方法,這個mixin都會被應用。
需要注意的是 lockMixinlockableAdvisor都是prototype的。
myAttributeAwarePointcut切點可以是一個單例定義,因為它不會持有被advise物件的個性狀態。

使用TargetSources

Spring提供了一個TargetSource概念,由org.springframework.aop.TargetSource介面所表示。
該介面負責返回實現了連線點的目標物件。
【待定】每次AOP代理處理一個方法呼叫時,TargetSource`實現都需要一個目標的例項。

開發人員使用Spring AOP通常不需要直接使用TargetSource,但是它提供了一個強大的供給池、熱替換和其他複雜的目標。
例如,一個池化的TargetSource可以為每次呼叫返回不同的目標示例,通過池子來管理這些例項。

如果你沒有指定一個TargetSource,預設的實現手段是使用一個包裝的本地物件。
每次呼叫返回的是同一個目標(如你所願)。

讓我們看一個Spring提供的標準TargetSource,以及如何使用它們。

提示

當使用一個自定義的TargetSource時,你的目標通常是一個prototype bean定義,而不是單例bean定義。
這允許Spring在需要時建立一個新的目標例項。

熱替換TargetSource

org.springframework.aop.target.HotSwappableTargetSource的存在,允許一個AOP代理的目標進行切換,同時允許呼叫者持有她們的引用。

修改TargetSource的目標物件會立即生效。HotSwappableTargetSource是執行緒安全的。

你可以如下所示,通過HotSwappableTargetSourceswap()方法修改目標物件:

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>

上面的呼叫的