原文連接html
在本附錄中,咱們會討論一些初級的Spring AOP接口,以及在Spring 1.2應用中所使用的AOP支持。
對於新的應用,咱們推薦使用 Spring AOP 2.0來支持,在AOP章節有介紹。但在已有的項目中,或者閱讀數據或者文章時,可能會遇到Spring AOP 1.2風格的示例。Spring 2.0徹底兼容Spring 1.2,在本附錄中全部的描述都是Spring 2.0所支持的。java
Spring中的切入點API
一塊兒看一下Spring是如何處理關鍵切入點這個概念。web
概念
Spring的切入點模型可以讓切入點重用不一樣的獨立的加強類型。這樣能夠實現,針對不一樣的加強,使用相同的切入點。正則表達式
org.springframework.aop.Pointcut
是一個核心接口,用於將加強定位到特定的類或者方法上。
完整的接口信息以下:spring
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
將Pointcut
拆分紅兩個部分,容許重用類和方法匹配的部分,和細粒度的組合操做(例如和其餘的方法匹配器執行一個「組合」操做)。數據庫
ClassFilter
接口用於將切點限制在給定的目標類上。
若是matches()
方法老是返回true,全部的類都會被匹配上。編程
public interface ClassFilter { boolean matches(Class clazz); }
MethodMatcher
接口一般更爲重要。完整的接口描述以下:c#
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)
方法。
這樣使得在目標加強執行前,一個切點能夠在方法執行時當即查看入參。api
大部分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 } }
最後一個示例描述了,一個類中若是聲明兩個方法,能夠同時處理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 } }
說明
若是一個異常拋出加強自己拋出了一個異常,它將覆蓋掉原始的異常(例如,改變拋給用戶的異常)。
這個覆蓋的異常一般是一個運行時異常;這樣就能夠兼容任何的方法簽名。
可是,若是一個異常拋出加強拋出了一個檢查時異常,這個異常必須和該目標方法的聲明匹配,以此在必定程度上與特定的目標籤名相結合。
不要拋出與目標方法簽名不兼容的檢查時異常!
提示
異常拋出加強能夠被任意切點使用。
後置加強
後置加強必須實現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類。
例如,它能夠被MethodInterceptor
、BeforeAdvice
和ThrowsAdvice
使用。
在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
的實現同樣,間接的引入了一個層次。
若是你定義了一個名爲foo
的ProxyFactoryBean
,那麼對象引用的foo
,不是ProxyFactoryBean
實例自己,
而是ProxyFactoryBean
對象調用getObject()
方法的返回值。
這個方法會建立一個AOP代理來包裝目標對象。
使用一個ProxyFactoryBean
或者IoC感知類來建立AOP代理的最大好處之一是,加強和切點一樣也能夠被IoC管理。
這是一個強大的功能,實現的方法是其餘AOP框架難以企及的。
JavaBean屬性
與大多數Spring所提供的FactoryBean
實現相同的是,ProxyFactoryBean
自己也是一個JavaBean。
它的屬性用於:
- 指定你想要代理的目標
- 指定是否須要使用CGLIB(參照下面的介紹和基於JDK和CGLIB的代理,)
一些關鍵的屬性繼承自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
屬性指定一系列的攔截器。
須要注意的是即便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
屬性,效果是同樣的。
然而,自動檢測顯然減小了工做量,也不容易出現拼寫錯誤。
代理接口
咱們一塊兒看一個簡單的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
標誌,在這種狀況下, Advised
的isFrozen()
方法會返回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"/>
DefaultAdvisorAutoProxyCreator
bean(命名不是重點,甚至能夠省略)定義會獲取全部在當前應用上下文中符合的切點。
在這種狀況下,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都會被應用。
須要注意的是 lockMixin
和lockableAdvisor
都是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
是線程安全的。
你能夠以下所示,經過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()
方法,修改了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的經典用法