天天看點

Spring 5 中文解析核心篇-IoC容器之Spring AOP API

上一章通過

@AspectJ

和基于

schema

的切面定義描述了Spring對AOP的支援。在本章中,我們讨論了較低級别的Spring AOP API。對于常見的應用程式,我們建議将Spring AOP與AspectJ切入點一起使用,如上一章所述。

6.1 本節描述了Spring如何處理關鍵切入點概念。

6.1.1 概念

Spring的切入點模型使切入點重用不受通知類型的影響。你可以使用相同的切入點來定位不同的通知。

org.springframework.aop.Pointcut

接口是核心接口,用于将通知定向到特定的類和方法。完整的接口如下:

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}           

Pointcut

接口分為兩部分,可以重用類和方法比對的部分以及細粒度的合成操作(例如與另一個方法比對器執行“聯合”)。

ClassFilter

接口用于将切入點限制為給定的一組目标類。如果

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

matchs(Method,Class)

方法用于測試此切入點是否與目标類上的給定方法比對。建立AOP代理時可以執行此評估,以避免需要對每個方法調用進行測試。如果兩個參數的

match

方法對于給定的方法傳回

true

,并且

MethodMatcher

isRuntime()

方法傳回

true

,則在每次方法調用時都将調用三個參數的

match

方法。這樣,切入點就可以在執行目标通知之前立即檢視傳遞給方法調用的參數。

大多數

MethodMatcher

實作都是靜态的,這意味着它們的

isRuntime()

false

。在這種情況下,永遠不會調用三參數比對方法。

如果可能,請嘗試使切入點成為靜态,以允許AOP架構在建立AOP代理時緩存切入點評估的結果。

6.1.2 切入點的操作

Spring支援切入點上的操作(特别是聯合和交集)。

聯合表示兩個切入點比對其中一個的方法。交集是指兩個切入點都比對的方法。聯合通常更有用。你可以通過使用

org.springframework.aop.support.Pointcuts

類中的靜态方法或使用同一包中的

ComposablePointcut

類來組成切入點。但是,使用AspectJ切入點表達式通常是一種更簡單的方法。但是,使用AspectJ切入點表達式通常是一種更簡單的方法。

6.1.3 AspectJ 表達式切入點

從2.0開始,Spring使用的最重要的切入點類型是

org.springframework.aop.aspectj.AspectJExpressionPointcut

。這是一個切入點,該切入點使用AspectJ提供的庫來解析AspectJ切入點表達式字元串。

有關支援的AspectJ切入點原語的讨論,

請參見上一章

6.1.4 便捷切入點實作

Spring提供了幾種友善的切入點實作。你可以直接使用其中一些。其他的則打算在特定于應用程式的切入點中被子類化。

靜态切入點

靜态切入點基于方法和目标類,并且不能考慮方法的參數。靜态切入點足以滿足大多數用途,并且最好。首次調用方法時,Spring隻能評估一次靜态切入點。之後,無需在每次方法調用時再次評估切入點(備注:第一次評估後進行緩存)。

本節的其餘部分描述了Spring附帶的一些靜态切入點實作。

正規表達式切入點

指定靜态切入點的一種明顯方法是正規表達式。除了Spring之外,還有幾個AOP架構使之成為可能。

org.springframework.aop.support.JdkRegexpMethodPointcut

是一個通用的正規表達式切入點它使用JDK中的正規表達式支援。

使用

JdkRegexpMethodPointcut

類,可以提供模式字元串的清單。如果其中任何一個比對,則切入點的評估結果為

true

。(是以,結果實際上是這些切入點的并集。)

以下示例顯示如何使用

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

可以是攔截器、前置通知、異常通知等)。在幕後,Spring使用了

JdkRegexpMethodPointcut

。使用

RegexpMethodPointcutAdvisor

簡化了連接配接,因為一個bean同時封裝了切入點和通知,如下面的示例所示:

<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

類型一起使用。

屬性驅動的切入點

靜态切入點的一種重要類型是中繼資料驅動的切入點。這将使用中繼資料屬性的值(通常是源級别的中繼資料)。

動态切入點

動态切入點比靜态切入點更昂貴。它們考慮了方法參數以及靜态資訊。這意味着必須在每次方法調用時對它們進行評估,并且由于參數會有所不同,是以無法緩存結果。

主要示例是

control flow

切入點。

控制流切入點

Spring控制流切入點在概念上類似于AspectJ

cflow

切入點,盡管功能不那麼強大。(目前還沒有辦法指定一個切入點在與另一個切入點比對的連接配接點下面執行。)控制流切入點與目前調用堆棧比對。例如,如果連接配接點是由

com.mycompany.web

包中的方法或

SomeCaller

類調用的,則可能會觸發。通過使用

org.springframework.aop.support.ControlFlowPointcut

類指定控制流切入點。通過使用

org.springframework.aop.support.ControlFlowPointcut

類指定控制流切入點。

與其他動态切入點相比,控制流切入點在運作時進行評估要昂貴得多。在Java 1.4中,成本大約是其他動态切入點的五倍。

6.1.5 切入點超類

Spring提供了有用的切入點超類,以幫助你實作自己的切入點。因為靜态切入點最有用,是以你可能應該子類化

StaticMethodMatcherPointcut

。這僅需要實作一個抽象方法(盡管你可以覆寫其他方法以自定義行為)。下面的示例顯示如何對

StaticMethodMatcherPointcut

進行子類化:

class TestStaticPointcut extends StaticMethodMatcherPointcut {

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

動态切入點也有超類。你可以将自定義切入點與任何通知類型一起使用。

6.1.6 自定義切面

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

更高版本的Spring可能提供對JAC提供的“語義切入點”的支援,例如“所有更改目标對象中執行個體變量的方法”。
6.2 Spring中的通知API

現在,我們可以檢查Spring AOP如何處理通知。

6.2.1 通知生命周期

每個通知都是一個Spring bean。通知執行個體可以在所有通知對象之間共享,或者對于每個通知對象都是唯一的。這對應于每個類或每個執行個體的通知。

每個類通知最常用。适用于一般通知,例如事物

advisors

(切面和通知組合)。這些不依賴于代理對象的狀态或添加新狀态。它們僅作用于方法和參數。

每個執行個體的通知都适合引入,以支援

mixins

。在這種情況下,通知将狀态添加到代理對象。

你可以在同一AOP代理中混合使用共享通知和基于執行個體的通知。

6.2.2 Spring中通知類型

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

攔截環繞通知

Spring中最基本的通知類型是環繞通知的攔截。

對于使用方法攔截的通知,Spring符合AOP Alliance接口。實作

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

就像其他的環繞通知一樣,可以傳回不同的值或引發異常,而不是調用

proceed

方法。但是,你沒有充分的理由就不要這樣做。

MethodInterceptor

實作提供與其他符合AOP Alliance要求的AOP實作的互操作性。本節其餘部分讨論的其他通知類型将實作常見的AOP概念,但以特定于Spring的方式。盡管使用最具體的通知類型有一個優勢,但是如果你可能想在另一個AOP架構中運作切面,則在環繞通知使用

MethodInterceptor

。請注意,切入點目前無法在架構之間互操作,并且AOP Alliance目前未定義切入點接口。

前置通知

一種最簡單的通知類型是前置通知。這個不需要

MethodInvocation

對象,因為它僅僅在進入方法前被調用。

前置通知的主要優點在于,無需調用

proceed()

方法,是以,不會因疏忽而未能沿攔截器鍊繼續前進。

以下清單顯示了

MethodBeforeAdvice

接口:

public interface MethodBeforeAdvice extends BeforeAdvice {

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

(盡管通常的對象适用于字段攔截,并且Spring不太可能實作它,但Spring的API設計允許前置通知。)

請注意,傳回類型為

void

。通知可以在連接配接點執行之前插入自定義行為,但不能更改傳回值。如果前置的通知引發異常,它将中止攔截器鍊的進一步執行。異常會傳播回攔截器鍊。如果是未檢查異常在調用的方法的簽名上,則将其直接傳遞給用戶端。否則,它将被AOP代理包裝在未經檢查的異常中。

以下示例顯示了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;
    }
}           
前置通知可以被使用于任何切入點。

異常通知

如果連接配接點引發異常,則在連接配接點傳回後調用引發通知。Spring提供抛出異常通知。注意這意味着

org.springframework.aop.ThrowsAdvice

接口不包含任何方法。這是一個标記接口,表示這個對象實作一個或多個抛出異常通知方法。這些應采用以下形式:

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

僅最後一個參數是必需的。方法簽名可以具有一個或四個參數,具體取決于通知方法是否對該方法參數感興趣。接下來的兩個清單顯示類,它們是抛出異常通知的示例。

如果引發

RemoteException

(包括從子類),則調用以下通知:

public class RemoteThrowsAdvice implements ThrowsAdvice {

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

與前面的通知不同,下一個示例聲明了四個參數,以便可以通路被調用的方法、方法參數和目标對象。如果抛出

ServletException

,則調用以下通知:

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

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

最後一個示例說明如何在處理

RemoteException

ServletException

的單個類中使用這兩種方法。可以将任意數量的異常通知方法組合到一個類中。以下清單顯示了最後一個示例:

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

方法抛出一個已檢查的異常,則它必須與目标方法的聲明異常比對,是以在某種程度上與特定的目标方法簽名耦合。不要抛出與目标方法簽名不相容的未聲明的檢查異常!

異常通知可以被使用與任何切入點。

後置傳回通知

在Spring中,後置傳回通知必須實作

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

從AOP Alliance

MethodInterceptor

接口繼承的

invoke()

方法必須實作引入。也就是說,如果被調用的方法在引入的接口上,則引入攔截器負責處理方法調用,不能調用

proceed()

引入通知不能與任何切入點一起使用,因為它僅适用于類,而不适用于方法級别。你隻能通過

IntroductionAdvisor

使用引入通知,它具有以下方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
           

沒有

MethodMatcher

,是以沒有與引入通知相關的

Pointcut

。隻有類過濾是合乎邏輯的。

getInterfaces()

方法傳回此

advisor

引入的接口。

在内部使用

validateInterfaces()

方法來檢視引入的接口是否可以由配置的

IntroductionInterceptor

實作。

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

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

這說明了混合。我們希望能夠将通知對象強制轉換為

Lockable

,無論它們的類型和調用鎖和解鎖方法如何。如果我們調用

lock()

方法,我們希望所有的

setter

方法都抛出一個

LockedException

。是以,我們可以添加一個切面,使對象在不了解對象的情況下不可變:AOP的一個很好的例子。

首先,我們需要一個

IntroductionInterceptor

來完成繁重的工作。在這種情況下,我們擴充了

org.springframework.aop.support.DelegatingIntroductionInterceptor

便利類。我們可以直接實作

IntroductionInterceptor

,但是在大多數情況下,最好使用

DelegatingIntroductionInterceptor

DelegatingIntroductionInterceptor

被設計為将引入委托給所引入接口的實際實作,而隐藏了監聽的使用。你可以使用構造函數參數将委托設定為任何對象。預設委托(使用無參數構造函數時)是

this

。是以,在下一個示例中,委托是

DelegatingIntroductionInterceptor

LockMixin

子類。給定一個委托(預設情況下為本身),

DelegatingIntroductionInterceptor

執行個體将查找由委托實作的所有接口(

IntroductionInterceptor

除外),并支援針對其中任何一個的引入。諸如

LockMixin

的子類可以調用

suppressInterface(Class intf)

方法來禁止不應公開的接口。但是,無論

IntroductionInterceptor

準備支援多少個接口,

IntroductionAdvisor

被使用控制實際公開哪些接口。引入的接口隐藏了目标對同一接口的任何實作。

是以,

LockMixin

擴充了

DelegatingIntroductionInterceptor

并實作了

Lockable

本身。超類會自動選擇可以支援

Lockable

進行引入的方法,是以我們不需要指定它。我們可以通過這種方式引入任意數量的接口。

注意

locked

執行個體變量的使用。這有效地将附加狀态添加到目标對象中儲存。

下面的示例顯示

LockMixin

類:

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

建立它。以下示例顯示了我們的

LockMixinAdvisor

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我們可以非常簡單地應用此

advisor

程式,因為它不需要配置。(但是,如果沒有

IntroductionAdvisor

,則無法使用

IntroductionInterceptor

。)像通常的介紹一樣,

advisor

必須是按執行個體的,因為它是有狀态的。對于每個被通知的對象,我們需要一個

LockMixinAdvisor

的不同執行個體,是以也需要

LockMixin

的不同執行個體。

advisor

包含被通知對象狀态的一部分。我們可以像其他任何

advisor

一樣,通過使用

Advised.addAdvisor()

方法或XML配置(推薦方式)以程式設計方式應用此

advisor

。下文讨論的所有代理建立選擇,包括“自動代理建立器”,都可以正确處理引入和有狀态的混合。

6.3 在Spring中的Advisor API

在Spring中,

Advisor

是隻包含一個與切入點表達式關聯的通知對象的切面。

除了介紹的特殊情況外,任何

advisor

都可以與任何通知一起使用。

org.springframework.aop.support.DefaultPointcutAdvisor

是最常用的

advisor

類。它可以與

MethodInterceptor

BeforeAdvice

ThrowsAdvice

一起使用。

可以在Spring中将

advisor

advice

類型混合在同一個AOP代理中。在一個代理配置中,可以使用環繞通知、異常通知和前置通知的攔截。Spring自動建立必要的攔截器鍊。

6.4 使用

ProxyFactoryBean

建立AOP代理

如果你的業務對象使用Spring IoC容器(一個

ApplicationContext

BeanFactory

)(你應該這樣做!),那麼你想使用Spring的AOP

FactoryBean

實作之一。(請記住,工廠bean引入了一個間接層,使它可以建立其他類型的對象。)

Spring AOP支援還在背景使用了工廠bean。

在Spring中建立AOP代理的基本方法是使用

org.springframework.aop.framework.ProxyFactoryBean

。這樣可以完全控制切入點,任何适用的通知及其順序。但是,如果不需要這樣的控制,則有一些更簡單的選項比較可取。

6.4.1 基礎

像其他Spring

FactoryBean

實作一樣,

ProxyFactoryBean

引入了一個間接級别。如果定義一個名為

foo

ProxyFactoryBean

,則引用

foo

的對象将看不到

ProxyFactoryBean

執行個體本身,而是看到由

ProxyFactoryBean

中的

getObject()

方法的實作建立的對象。此方法建立一個包裝目标對象的AOP代理。

ProxyFactoryBean

或另一個支援IoC的類建立AOP代理的最重要好處之一是通知和切入點也可以由IoC管理。這是一項強大的功能,可以實作某些其他AOP架構難以實作的方法。例如,通知本身可以引用應用程式對象(除了目标對象,它應該在任何AOP架構中都可用),這得益于依賴注入提供的所有可插入性。

6.4.2 JavaBean屬性

與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

    : 如果代理配置被當機,則不再允許對配置進行更改。當你不希望調用者在建立代理後(通過已通知接口)能夠操作代理時,這對于輕微的優化是非常有用的。此屬性的預設值為

    false

    ,是以允許進行更改(例如添加其他通知)。
  • exposeProxy

    :确定目前代理是否應該在

    ThreadLocal

    中暴露,以便目标可以通路它。如果目标需要擷取代理,并且暴露代理屬性設定為

    true

    ,則目标可以使用

    AopContext.currentProxy()

ProxyFactoryBean

特有的其他屬性包括:

  • proxyInterfaces

    :字元串接口名稱的數組。如果未提供,則使用目标類的CGLIB代理(另請參見
  • interceptorNames

    :

    Advisor

    ,攔截器或要應用的其他通知名稱的字元串數組。順序很重要,先到先得。也就是說,清單中的第一個攔截器是第一個能夠攔截調用的攔截器。
  • 你可以在攔截器名稱後加上星号(

    *

    )。這樣做會導緻所有

    advisor

    bean的應用程式的名稱都以要應用的星号之前的部分開頭。你可以在使用 Global Advisors 中找到使用此特性的示例。
  • singleton

    :無論

    getObject()

    方法被調用的頻率如何,工廠是否應傳回單個對象。一些

    FactoryBean

    實作提供了這種方法。預設值是

    true

    。如果你想使用有狀态通知—例如,有狀态混合—使用原型通知和單例值

    false

6.4.3 基于JDK和CGLIB代理

本部分是有關

ProxyFactoryBean

如何選擇為特定目标對象(将被代理)建立基于JDK的代理或基于CGLIB的代理的權威性文檔。

在Spring的1.2.x版和2.0版之間,

ProxyFactoryBean

的行為與建立基于JDK或CGLIB的代理有關。現在,

ProxyFactoryBean

在自動檢測接口方面展示了與

TransactionProxyFactoryBean

類類似的語義。

如果要代理的目标對象的類(以下簡稱為目标類)沒有實作任何接口,則建立基于CGLIB的代理。這是最簡單的情況,因為JDK代理是基于接口的,并且沒有接口意味着甚至無法進行JDK代理。你可以插入目标bean并通過設定

interceptorNames

屬性來指定攔截器清單。請注意,即使

ProxyFactoryBean

proxyTargetClass

屬性已設定為

false

,也會建立基于CGLIB的代理。(這樣做沒有任何意義,最好将其從bean定義中删除,因為它充其量是多餘,并且在最糟的情況下會造成混淆。)

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

ProxyFactoryBean

的配置。

ProxyFactoryBean

proxyTargetClass

true

,則将建立基于CGLIB的代理。這很有道理,也符合最小意外原則。即使

ProxyFactoryBean

proxyInterfaces

屬性被設定為一個或多個完全限定的接口名,

proxyTargetClass

屬性被設定為

true

也會使基于cglib的代理生效。

ProxyFactoryBean

proxyInterfaces

屬性被設定為一個或多個完全限定的接口名稱,那麼将建立一個基于jdk的代理。建立的代理實作了

proxyInterfaces

屬性中指定的所有接口。如果目标類碰巧實作了比

proxyInterfaces

屬性中指定的更多的接口,那也沒什麼問題,但是那些額外的接口不是由傳回的代理實作的。

如果沒有設定

ProxyFactoryBean

proxyInterfaces

屬性,但是目标類實作了一個(或多個)接口,那麼

ProxyFactoryBean

會自動檢測到目标類确實實作了至少一個接口,并建立一個基于jdk的代理。實際代理的接口是目标類實作的所有接口。實際上,這與将目标類實作的每個接口的清單提供給

proxyInterfaces

屬性相同。然而,這大大減少了工作量,也不容易出現書寫錯誤。

6.4.4 代理接口

考慮一個簡單的

ProxyFactoryBean

操作示例。此示例涉及:

  • 代理的目标bean。這是示例中的

    personTarget

    bean定義。
  • 用于提供通知的

    Advisor

    和攔截器。
  • 一個用于指定目标對象(

    personTarget

    bean)、代理接口和應用通知的AOP代理bean定義。

以下清單顯示了示例:

<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

屬性接受一個字元串清單,其中包含目前工廠中的攔截器或

advisors

的bean名稱。你可以使用

advisors

、攔截器、前置通知、後置通知和異常通知對象。

advisors

的順序很重要。

你可能想知道為什麼清單不儲存bean引用。這樣做的原因是,如果

ProxyFactoryBean

singleton

false

,那麼它必須能夠傳回獨立的代理執行個體。如果任何

advisors

本身是原型,則需要傳回一個獨立的執行個體,是以必須能夠從工廠獲得原型的執行個體。

可以使用前面顯示的

person

Bean定義代替

Person

實作,如下所示:

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

與普通Java對象一樣,在同一IoC上下文中的其他bean可以表達對此的強類型依賴性。以下示例顯示了如何執行此操作:

<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

類型的對象。如果我們想防止應用程式上下文的使用者獲得對未通知對象的引用,或者需要避免使用Spring IoC自動裝配産生歧義,這是很有用的。可以說,還有一個優點是

ProxyFactoryBean

定義是獨立的。然而,有時候,能夠從工廠獲得未通知的目标實際上可能是一種優勢(例如,在某些測試場景中)。

6.4.5 代理類

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

想象一下,在我們之前的示例中,沒有

Person

接口。我們需要通知一個名為

Person

的類,該類沒有實作任何業務接口。在這種情況下,你可以将Spring配置為使用CGLIB代理而不是動态代理。為此,請将前面顯示的

ProxyFactoryBean

proxyTargetClass

屬性設定為

true

。雖然最好是根據接口而不是類程式設計,但在處理遺留代碼時,通知沒有實作接口的類的能力可能很有用。(一般來說,Spring是沒有規定性的。雖然它使應用良好實踐變得容易,但它避免了強制使用特定的方式或方法。)

如果需要,即使有接口,也可以在任何情況下強制使用CGLIB。

CGLIB代理通過在運作時生成目标類的子類來工作。Spring配置此生成的子類以将方法調用委托給原始目标。子類用于實作

Decorator

模式,并編織在通知中。

CGLIB代理通常應對使用者透明。但是,有一些問題要考慮:

  • final

    的方法不能被通知,因為它們不能被覆寫(備注:子類不能覆寫被

    final

    标記方法)。
  • 無需将CGLIB添加到你的類路徑中。從Spring 3.2開始,CGLIB被重新打包并包含在

    spring-core

    JAR中。換句話說,基于CGLIB的AOP就像JDK動态代理一樣“開箱即用”。

CGLIB代理和動态代理之間幾乎沒有性能差異。

在這種情況下,性能不應作為決定性的考慮因素。

6.4.6 使用

全局

Advisors

通過向攔截器名稱附加星号,所有具有與星号之前的部分相比對的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"/>           
參考代碼:

com.liyong.ioccontainer.starter.ProxyFactoryBeanIocContainer

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工廠)預執行個體化所有單例。是以,重要的是(至少對于單例bean),如果你有一個(父)bean定義僅打算用作模闆,并且此定義指定了一個類,則必須確定将

abstract

true

。否則,應用程式上下文實際上會嘗試對其進行執行個體化。

6.6 通過

ProxyFactory

程式設計式地建立AOP代理

使用Spring以程式設計方式建立AOP代理很容易。這使你可以使用Spring AOP,而無需依賴Spring IoC。

由目标對象實作的接口将被自動代理。以下清單顯示了使用一個攔截器和一個

advisor

為目标對象建立代理的過程:

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

第一步是構造一個類型為

org.springframework.aop.framework.ProxyFactory

的對象。你可以使用目标對象(如前面的示例所示)來建立它,也可以在替代構造函數中指定要代理的接口。

你可以添加通知(攔截器是一種專門的通知)、

advisors

,或者同時添加它們,并在

ProxyFactory

的生命周期中操作它們。如果添加了

IntroductionInterceptionAroundAdvisor

,則可以使代理實作其他接口。

ProxyFactory

上還有一些友善的方法(繼承自

AdvisedSupport

),可以添加其他通知類型,比如

before

throw advice

AdvisedSupport

ProxyFactory

ProxyFactoryBean

的超類。

在大多數應用程式中,将AOP代理建立與IoC架構內建在一起是最佳實踐。通常,建議你使用AOP從Java代碼外部化配置。
6.7 操作通知對象

無論如何建立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

,則此索引處傳回的

advisor

是你添加的對象。如果添加了攔截器或其他通知類型,Spring會将其包裝在帶有指向總是傳回

true

的切入點的

advisor

中。是以,如果你添加一個

MethodInterceptor

,為這個索引傳回的

advisor

是一個

DefaultPointcutAdvisor

,它傳回你的

MethodInterceptor

和一個比對所有類和方法的切入點。

addAdvisor()

方法可用于添加任何

Advisor

。通常,持有切入點和通知的

advisor

是通用的

DefaultPointcutAdvisor

,你可以将其用于任何通知或切入點(但不用于

introduction

)。

預設情況下,即使已建立代理,也可以添加或删除

advisor

或攔截器。唯一的限制是不可能添加或删除一個

introduction

advisor

,因為工廠中的現有代理不會顯示接口更改。(你可以從工廠擷取新的代理來避免此問題。)

以下示例顯示了将AOP代理投射到

Advised

接口并檢查和處理其通知:

Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

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

根據建立代理的方式,通常可以設定當機标志。在這種情況下,

Advised

isFrozen()

true

,并且任何通過添加或删除來修改通知的嘗試都會導緻

AopConfigException

。當機已通知對象狀态的能力在某些情況下非常有用(例如,防止調用代碼删除安全攔截器)。

6.8 使用“

自動代理

”功能

到目前為止,我們已經考慮過通過使用

ProxyFactoryBean

或類似的工廠bean來顯式建立AOP代理。

Spring還允許我們使用“

自動代理

” Bean定義,該定義可以自動代理標明的Bean定義。它建構在Spring的bean後處理器基礎設施上,該基礎設施允許在裝載容器時修改任何bean定義。

在這個模型中,你在XML bean定義檔案中設定了一些特殊的bean定義來配置自動代理基礎設施。這使你可以聲明有資格進行自動代理的目标。你無需使用

ProxyFactoryBean

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

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

6.8.1 自定代理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

屬性而不是一列攔截器,以允許原型

advisors

的正确行為。名為“攔截器”的可以是

advisors

或任何通知類型。

一般而言,與自動代理一樣,使用

BeanNameAutoProxyCreator

的要點是将相同的配置一緻地應用于多個對象,并且配置量最少。将聲明式事務應用于多個對象是一種流行的選擇。

名稱比對的Bean定義,例如前面示例中的

jdkMyBean

onlyJdk

,是帶有目标類的普通舊Bean定義。

BeanNameAutoProxyCreator

自動建立一個AOP代理。相同的通知适用于所有比對的bean。注意,如果使用了

advisors

(而不是前面的示例中的攔截器),則切入點可能會不同地應用于不同的bean。

DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator

是更通用,功能極其強大的自動代理建立器。這将自動在目前上下文中應用合格的

advisor

,而不需要在自動代理

advisor

bean定義中包含特定的bean名稱。與

BeanNameAutoProxyCreator

一樣,它具有一緻的配置和避免重複的優點。

使用此機制涉及:

  • 指定

    DefaultAdvisorAutoProxyCreator

  • 在相同或關聯的上下文中指定任何數量的

    advisor

    。請注意,這些必須是

    advisor

    ,而不是攔截器或其他通知。這是必要的,因為必須有一個要評估的切入點來檢查每個通知到候選bean定義的資格。

    DefaultAdvisorAutoProxyCreator

    自動評估每個

    advisor

    中包含的切入點,以檢視應該将什麼(如果有的話)通知應用到每個業務對象(例如示例中的

    businessObject1

    businessObject2

這意味着可以将任意數量的

advisor

自動應用于每個業務對象。如果任何

advisor

中沒有切入點比對業務對象中的任何方法,則該對象不會被代理。當為新的業務對象添加Bean定義時,如有必要,它們會自動被代理。

通常,自動代理的優點是使調用者或依賴者無法獲得未通知的對象。在此

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

如果要将相同的通知一緻地應用于許多業務對象,則

DefaultAdvisorAutoProxyCreator

非常有用。一旦基礎設施定義就位,你就可以添加新的業務對象,而不包括特定的代理配置。你還可以很容易地删除其他切面(例如,跟蹤或性能監視切面),隻需對配置進行最小的更改。

DefaultAdvisorAutoProxyCreator

支援過濾(通過使用命名約定,隻有特定的

advisor

被評估,這允許在同一個工廠中使用多個不同配置的

AdvisorAutoProxyCreators

)和排序。

Advisor

可以實作

org.springframework.core.Ordered

接口,以確定在出現問題時可以正确排序。前面示例中使用的

TransactionAttributeSourceAdvisor

具有可配置的順序值。預設設定為無序。

com.liyong.ioccontainer.starter.AdvisorAutoProxyCreatorIocContainer

6.9 使用

TargetSource

實作

Spring提供了

TargetSource

的概念,以

org.springframework.aop.TargetSource

接口表示。該接口負責傳回實作連接配接點的“

目标對象

”。每當AOP代理處理方法調用時,就會向

TargetSource

實作詢問目标執行個體。

使用Spring AOP的開發人員通常不需要直接使用

TargetSource

實作,但是這提供了支援池、熱交換和其他複雜目标的強大方法。例如,通過使用池來管理執行個體,

TargetSource

可以為每次調用傳回不同的目标執行個體。

如果未指定

TargetSource

,則将使用預設實作包裝本地對象。每次調用都傳回相同的目标(與你期望的一樣)。

本節的其餘部分描述了Spring随附的标準目标源以及如何使用它們。

使用自定義目标源時,目标通常需要是原型而不是單例bean定義。這樣,Spring可以在需要時建立一個新的目标執行個體。

6.9.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 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的引用的用戶端不知道更改,但會立即開始達到新目标。

盡管這個示例沒有添加任何通知(使用

TargetSource

不需要添加通知),但是可以将任何

TargetSource

與任意通知結合使用。

6.9.2 池目标源

使用池目标源可以提供類似于無狀态會話ejb的程式設計模型,其中維護相同執行個體的池,方法調用将釋放池中的對象。

Spring池和

SLSB

池之間的關鍵差別在于,Spring池可以應用于任何POJO。與Spring一般情況一樣,可以以非侵入性的方式應用此服務。

Spring提供對

Commons Pool 2.2

支援,它提供一個相當地高效池實作。你需要在你的應用類路徑上添加

commons-pool

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

)必須是原型。這使

PoolingTargetSource

實作可以建立目标的新執行個體,以根據需要去擴充池中對象。有關其屬性的資訊,請參見

AbstractPoolingTargetSource

的javadoc和希望使用的具體子類

maxSize

是最基本的,并且始終保證存在。

在這種情況下,

myInterceptor

是需要在同一IoC上下文中定義的攔截器的名稱。但是,你無需指定攔截器即可使用池。如果隻希望池化而沒有其他通知,則完全不要設定

interceptorNames

屬性。

你可以将Spring配置為能夠将任何池化對象轉換到

org.springframework.aop.target.PoolingConfig

接口,該接口通過

introduction

來公開有關池的配置和目前大小的資訊。

你需要定義類似于以下内容的

advisor

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

通過在

AbstractPoolingTargetSource

類上調用便捷方法來獲得此

advisor

,是以可以使用

MethodInvokingFactoryBean

。該

advisor

的名稱(在此處為

poolConfigAdvisor

)必須位于暴露池對象的

ProxyFactoryBean

中的攔截器名稱清單中。

轉換的定義如下:

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

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

TargetSource

6.9.3 原型目标源

設定“

原型

”目标源類似于設定池化

TargetSource

。在這種情況下,每次方法調用都會建立目标的新執行個體。盡管在現代JVM中建立新對象的成本并不高,但連接配接新對象(滿足其IoC依賴項)的成本可能會更高。是以,沒有充分的理由就不應使用此方法。

為此,你可以修改前面顯示的

poolTargetSource

定義,如下所示(為清楚起見,我們也更改了名稱):

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

唯一的屬性是目标Bean的名稱。在

TargetSource

實作中使用繼承來確定命名一緻。與池化目标源一樣,目标bean必須是原型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 定義新通知類型

Spring AOP被設計為可擴充的。雖然目前在内部使用攔截實作政策,但是除了在環繞通知、前置通知、異常通知以及在傳回通知進行攔截外,還可以支援任意的通知類型。

擴充卡包是一個SPI包,它允許在不更改核心架構的情況下添加對新的自定義通知類型的支援。對自定義

Advice

類型的唯一限制是它必須實作

org.aopalliance.aop.Advice

标記接口。

有關更多資訊,請參見

org.springframework.aop.framework.adapter

javadoc。

com.liyong.ioccontainer.starter.TargetSourceIocContainer

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。關注公衆号:青年IT男 擷取最新技術文章推送!

部落格位址:

http://youngitman.tech

CSDN:

https://blog.csdn.net/liyong1028826685

微信公衆号:

Spring 5 中文解析核心篇-IoC容器之Spring AOP API

技術交流群:

Spring 5 中文解析核心篇-IoC容器之Spring AOP API