《Spring 5官方文檔》 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() &amp;&amp; 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>

上面的調用的swap()方法,修改了swappable對象的目標對象。
持有這個bean引用的客戶端對此更改毫無感知,但是會立即開始命中新的目標對象。

盡管這個示例沒有添加任何advice,並且添加一個advice到一個使用的TargetSource中也是沒必要的,當然任何TargetSource都可以和任意的advice結合使用。

池化TargetSources

使用一個池化的TargetSource,提供了一個與無狀態會話的EJB類似的編程模型,池子中維護了相同類型的實例,當方法調用時釋放池子中的對象。

Spring池子和SLSB池子的關鍵區別在於,Spring池子可以適用於任意POJO類。通常和Spring一樣,這個服務可以用非侵入性的方式使用。

Spring提供了對框架外Commons Pool 2.2的支持,Commons Pool 2.2提供了一個相當有效的池子實現。
使用該特性,需要在應用的classpath中加入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”必須是prototype的。
這允許PoolingTargetSource在需要的時候創建為目標對象創建新的實例來擴張池子大小。
參考 AbstractPoolingTargetSource的Javadoc文檔,以及你要使用的具體的子類屬性信息:
“maxSize”是最基礎的,需要保證它存在

在本示例中,”myInterceptor”是一個攔截器的名稱,需要在同一個IoC上下文中被定義。
然而,不需要為使用的池子,指定攔截器。
如果你只需要池子,不需要任何advice,就不要設置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暴露的池化對象的攔截器列表中。

強制轉化如下所示:

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());

說明

池化無狀態的服務實例通常是不必要的。
我們不認為這是默認選擇,因為大多數的無狀態對象自然是縣城安全的,並且如果資源被緩存,實例池會存在問題。

簡單的池子也可以使用自動代理。可以使用任何自動代理創建器設置 TargetSource。

Prototype類型的TargetSource

設置一個”prototype”的TargetSource和池化一個TargetSource是類似的。
在本示例中,當每個方法調用時,都會創建一個目標的示例。
盡管在現代JVM中創建一個對象的成本不高,綁定一個新對象(滿足IoC的依賴)可能花費更多。
因此,沒有一個很好的理由,你不應該使用這個方法,。

為此,你可以修改上面定義的 poolTargetSource成如下所示(為了清楚起見,我也修改了命名):

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

只有一個屬性:目標bean的命名。在TargetSource實現中使用繼承是為了確保命名的一致性。
與池化TargetSource一樣,目標bean的定義也必須是prototype。

ThreadLocal的TargetSource

如果你需要為每一個進來的請求(每個線程一個那種)創建一個對象,那么ThreadLocal的TargetSource將會有所幫助。
JDK范疇提供ThreadLocal的概念是在線程上透明存儲資源的能力。
建立一個ThreadLocalTargetSource對其他類型的TargetSource,與該概念十分相似:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>

說明

當在多線程和多classload環境中,不正確的使用ThreadLocal時會出現一些問題(潛在的結果是內存泄漏)。
應當始終考慮將 ThreadLocal封裝在一些類(包裝類除外)中,不能直接使用 ThreadLocal 本身。
同樣的,應當始終記得為線程中的資源正確的使用set和unset(后者只涉及到調用一個ThreadLocal.set(null)方法)。
unset應當在任何情況都調用,因為不掉用unset可能會導致行為錯誤。
Spring的ThreadLocal支持該功能,應當始終考慮贊成使用ThreadLocal,沒有其他正確的處理代碼【待定】。

定義新的Advice類型

Spring AOP被設計為可擴展的。
雖然攔截器實現策略目前是在內部使用的,但是它可以支持框架之外任意類型的advice(攔截式環繞增強、前置增強、異常拋出增強和后置增強)。

org.springframework.aop.framework.adapter包是一個SPI包,在不改動核心框架的情況下,支持添加新的自定義advice類型。

自定義Advice只有一個約束,就是必須實現org.aopalliance.aop.Advice標簽接口。

請參閱org.springframework.aop.framework.adapter包的Javadoc文檔,獲取更多信息。

更多資源

有關Spring AOP的更多示例,請參閱Spring示例應用程序:

  • JPetStore的默認配置,展示了將TransactionProxyFactoryBean應用於聲明式事務管理。
  • JPetStore的/attributes目錄展示了使用屬性驅動聲明式事務管理。

原創文章,轉載請注明: 轉載自並發編程網 – ifeve.com本文鏈接地址: 《Spring 5官方文檔》37. Spring AOP的經典用法


FavoriteLoading添加本文到我的收藏 原文地址:http://ifeve.com/classic-aop-spring/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM