Spring AOP APIs
前一章介紹了Spring 2.0中提供的由@AspectJ和基于Schema的兩種切面定義的AOP。在這個(gè)章節(jié)里,我們將 討論更底層的Spring AOP API,以及如何在Spring 1.2應(yīng)用中使用這些API。對(duì)于新的應(yīng)用程序,我們推薦 使用前一章介紹的Spring 2.0 AOP支持,但是當(dāng)你使用已有系統(tǒng)時(shí),或是閱讀書籍和文章時(shí),很有可能會(huì)遇到 Spring 1.2風(fēng)格的例子。Spring 2.0是完全向前兼容Spring 1.2的,這一章中涉及的所有內(nèi)容在Spring 2.0里面得到了完全的支持。
讓我們看看Spring是如何處理切入點(diǎn)這個(gè)重要概念的。
Spring的切入點(diǎn)模型使得切入點(diǎn)可以獨(dú)立于通知類型進(jìn)行重用,這就使得針對(duì)不同 advice使用相同的pointcut成為可能。
org.springframework.aop.Pointcut 是最核心的接口,用來(lái)將 通知應(yīng)用于特定的類和方法,完整的接口定義如下:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
將Pointcut接口分割成有利于重用類和方法匹配的兩部分,以及進(jìn)行更細(xì)粒度的操作組合(例如與另一個(gè)方法匹配實(shí)現(xiàn)進(jìn)行“或操作”)。
ClassFilter接口用來(lái)將切入點(diǎn)限定在一個(gè)給定的類集合中。如果matches()方法總是返回true,所有目標(biāo)類都將被匹配:
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)方法用來(lái)測(cè)試這個(gè)切入點(diǎn)是否匹配目標(biāo)類的指定方法。這將在AOP代理被創(chuàng)建的時(shí)候執(zhí)行,這樣可以避免在每次方法調(diào)用的時(shí)候都執(zhí)行。如果matches(Method, Class )對(duì)于一個(gè)給定的方法返回true,并且isRuntime() 也返回true,那么matches(Method, Class , Object[])將在每個(gè)方法調(diào)用的時(shí)候被調(diào)用。這使得切入點(diǎn)在通知將被執(zhí)行前可以查看傳入到方法的參數(shù)。
大多數(shù)MethodMatcher是靜態(tài)的,這意味著isRuntime()方法返回 false。在這種情況下,matches(Method, Class , Object[])永遠(yuǎn)不會(huì)被調(diào)用。
![]() |
Tip |
---|---|
應(yīng)盡可能地使切入點(diǎn)是靜態(tài)的,這就允許AOP框架在AOP代理被創(chuàng)建時(shí)緩存對(duì)切入點(diǎn)的計(jì)算結(jié)果。 |
Spring在切入點(diǎn)上支持以下運(yùn)算: 或和與。
-
或運(yùn)算表示只需有一個(gè)切入點(diǎn)被匹配就執(zhí)行方法。
-
與運(yùn)算表示所有的切入點(diǎn)都匹配的情況下才執(zhí)行。
-
通常或運(yùn)算要更有用一些。
-
切入點(diǎn)可以使用org.springframework.aop.support.Pointcuts類中的靜態(tài)方法來(lái)編寫,或者使用同一個(gè)包內(nèi)的ComposablePointcut類。不過(guò)使用AspectJ切入點(diǎn)表達(dá)式通常會(huì)更簡(jiǎn)單一些。
從2.0開(kāi)始,Spring中使用的最重要的切入點(diǎn)類型是org.springframework.aop.aspectj.AspectJExpressionPointcut。 這個(gè)切入點(diǎn)使用AspectJ提供的庫(kù)來(lái)解析滿足AspectJ語(yǔ)法切入點(diǎn)表達(dá)式字符串。
可以查看前一章關(guān)于所支持的AspectJ切入點(diǎn)原語(yǔ)的討論。
Spring提供了一些很方便的切入點(diǎn)實(shí)現(xiàn)。一些是開(kāi)箱即用的,其它的是切入點(diǎn)應(yīng)用規(guī)范的子集。
靜態(tài)切入點(diǎn)基于方法和目標(biāo)類進(jìn)行切入點(diǎn)判斷而不考慮方法的參數(shù)。在多數(shù)情況下,靜態(tài)切入點(diǎn)是高效的、最好的選擇。 Spring只在第一次調(diào)用方法時(shí)執(zhí)行靜態(tài)切入點(diǎn):以后每次調(diào)用這個(gè)方法時(shí)就不需要再執(zhí)行。
讓我們考慮Spring中的一些靜態(tài)切入點(diǎn)實(shí)現(xiàn)。
顯而易見(jiàn),一種描述靜態(tài)切入點(diǎn)的方式是使用正則表達(dá)式。包含Spring在內(nèi)的一些AOP框架都支持這種方式。 org.springframework.aop.support.Perl5RegexpMethodPointcut是一個(gè)最基本的正則表達(dá)式切入點(diǎn), 它使用Perl 5正則表達(dá)式語(yǔ)法。Perl5RegexpMethodPointcut依賴Jakarta ORO進(jìn)行正則表達(dá)式匹配。 Spring也提供了JdkRegexpMethodPointcut類,它使用JDK 1.4或更高版本里提供的正則表達(dá)式支持。
使用Perl5RegexpMethodPointcut類,你可以提供一組模式字符串。 如果其中任意一個(gè)模式匹配,切入點(diǎn)將被解析為true。(實(shí)際上就是這些切入點(diǎn)的或集。)
下面顯示用法:
<bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
Spring提供了一個(gè)方便的類 RegexpMethodPointcutAdvisor, 它也允許我們引用一個(gè)通知(記住這里一個(gè)通知可以是攔截器,前置通知(before advice),異常通知(throws advice)等類型中的一個(gè))。 在背后,如果使用J2SE 1.4或者以上版本,Spring將使用JdkRegexpMethodPointcut,在之前版本的虛擬機(jī)上,Spring將退回去使用Perl5RegexpMethodPointcut。 可以通過(guò)設(shè)置perl5屬性為true來(lái)強(qiáng)制使用Perl5RegexpMethodPointcut。使用RegexpMethodPointcutAdvisor可以簡(jiǎn)化織入,因?yàn)橐粋€(gè)bean可以同時(shí)作為切入點(diǎn)和advisor,如下所示:
<bean id="settersAndAbsquatulateAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="beanNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
RegexpMethodPointcutAdvisor可以和任何通知類型一起使用
動(dòng)態(tài)切入點(diǎn)比起靜態(tài)切入點(diǎn)在執(zhí)行時(shí)要消耗更多的資源。它們同時(shí)計(jì)算方法參數(shù)和靜態(tài)信息。 這意味著它們必須在每次方法調(diào)用時(shí)都被執(zhí)行;由于參數(shù)的不同,評(píng)估結(jié)果不能被緩存。
動(dòng)態(tài)切入點(diǎn)的常見(jiàn)例子是控制流切入點(diǎn)。
Spring控制流切入點(diǎn)在概念上和AspectJ的cflow 切入點(diǎn)很相似, 雖然它的功能不如后者那么強(qiáng)大。(目前還不能讓一個(gè)切入點(diǎn)在另外一個(gè)切入點(diǎn)所評(píng)估的連接點(diǎn)處執(zhí)行)。 一個(gè)控制流切入點(diǎn)匹配當(dāng)前的調(diào)用棧。例如,一個(gè)連接點(diǎn)被com.mycompany.web包內(nèi)的一個(gè) 方法或者SomeCaller類調(diào)用,切入點(diǎn)就可能被激活。 控制流切入點(diǎn)是由org.springframework.aop.support.ControlFlowPointcut 類聲明的。
![]() |
Note |
---|---|
在執(zhí)行時(shí)控制流切入點(diǎn)的開(kāi)銷是非常昂貴的,甚至與其它動(dòng)態(tài)切入點(diǎn)比起來(lái)也是如此。在Java 1.4里,它的開(kāi)銷差不多是其它動(dòng)態(tài)切入點(diǎn)的5倍;在Java 1.3中,這個(gè)比例甚至達(dá)到10倍。 |
Spring提供了很多有用的切入點(diǎn)基礎(chǔ)類來(lái)幫助你實(shí)現(xiàn)你自己的切入點(diǎn)。
因?yàn)殪o態(tài)切入點(diǎn)是最常用的,你可能會(huì)像下面那樣繼承StaticMethodMatcherPointcut。這只需要實(shí)現(xiàn)一個(gè)抽象方法 (雖然也可以覆蓋其它方法來(lái)定制行為):
class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } }
動(dòng)態(tài)切入點(diǎn)也有很多基類。
你可以用Spring 1.0 RC2和更高版本里的自定義切入點(diǎn)及通知類型。
因?yàn)樵赟pring AOP中的切入點(diǎn)是Java類而不是語(yǔ)言的特性(后者像AspectJ中那樣),所以可以聲明自定義的切入點(diǎn),不論是靜態(tài)還是動(dòng)態(tài)的。自定義切入點(diǎn)在Spring里可能很強(qiáng)大。即使這樣我們?nèi)酝扑]盡可能使用AspectJ切入點(diǎn)表達(dá)式語(yǔ)言。
![]() |
Note |
---|---|
后續(xù)版本的Spring也許會(huì)提供“語(yǔ)義切入點(diǎn)”,像JAC所提供的那樣:例如,“所有方法可以修改目標(biāo)對(duì)象中實(shí)例變量” |
現(xiàn)在讓我們看一下SPring AOP是怎樣處理通知的。
每個(gè)通知都是一個(gè)Spring bean。一個(gè)通知實(shí)例既可以被所有被通知的對(duì)象共享,也可以被每個(gè)被通知對(duì)象獨(dú)占。 這根據(jù)設(shè)置類共享(per-class)或基于實(shí)例(per-instance)的參數(shù)來(lái)決定。
類共享通知經(jīng)常會(huì)被用到。它很適合用作通用的通知例如事務(wù)advisor。這些advisor不依賴于代理對(duì)象的狀態(tài)也不會(huì)向代理對(duì)象添加新的狀態(tài);它們僅僅在方法和參數(shù)上起作用。
基于實(shí)例的通知很適合用作導(dǎo)入器來(lái)支持混合類型。在這種情況下,通知向代理對(duì)象添加狀態(tài)。
在同一個(gè)AOP代理里混合使用類共享和基于實(shí)例的通知是可能的。
Spring提供了多種開(kāi)箱即用的通知類型,而且它們也可以被擴(kuò)展來(lái)支持任何通知類型。讓我們先看看基本概念和標(biāo)準(zhǔn)的通知類型。
在Spring中最基礎(chǔ)的通知類型是攔截around通知。
Spring里使用方法攔截的around通知兼容AOP聯(lián)盟接口。實(shí)現(xiàn)around通知的MethodInterceptor應(yīng)當(dāng)實(shí)現(xiàn)下面的接口:
public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }
invoke()方法的MethodInvocation參數(shù)暴露了被調(diào)用的方法; 目標(biāo)連接點(diǎn);AOP代理以及傳遞給方法的參數(shù)。invoke()方法應(yīng)該返回調(diào)用的結(jié)果:即連接點(diǎn)的返回值。
一個(gè)簡(jiǎn)單的MethodInterceptor實(shí)現(xiàn)看起來(lái)像下面這樣:
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; } }
注意對(duì)MethodInvocation中proceed()方法的調(diào)用。這個(gè)方法 繼續(xù)運(yùn)行指向連接點(diǎn)的攔截器鏈并返回proceed()的結(jié)果。然而,一個(gè)類似任何環(huán)繞通知的MethodInterceptor, 可以返回一個(gè)不同的值或者拋出一個(gè)異常而不是調(diào)用proceed方法。但除非你有很好的理由,否則不要考慮這樣做!
![]() |
Note |
---|---|
MethodInterceptor提供了與其它AOP聯(lián)盟兼容實(shí)現(xiàn)的互操作性。本節(jié)的剩下部分將討論其它的通知類型,它們實(shí)現(xiàn)了通用的AOP概念, 但是以一種Spring風(fēng)格的方式來(lái)實(shí)現(xiàn)的。使用最通用的通知類型還有一個(gè)好處,固定使用MethodInterceptor 環(huán)繞通知可以讓你在其它的AOP框架里 運(yùn)行你所定制的方面。要注意現(xiàn)在切入點(diǎn)還不能和其它框架進(jìn)行互操作,AOP聯(lián)盟目前還沒(méi)有定義切入點(diǎn)接口。 |
一個(gè)更簡(jiǎn)單的通知類型是before 通知。它不需要 MethodInvocation對(duì)象,因?yàn)樗皇窃谶M(jìn)入方法之前被調(diào)用。
前置通知(before advice)的一個(gè)主要優(yōu)點(diǎn)是它不需要調(diào)用proceed() 方法,因此就不會(huì)發(fā)生 無(wú)意間運(yùn)行攔截器鏈?zhǔn)〉那闆r。
MethodBeforeAdvice 接口被顯示在下面。(Spring的API設(shè)計(jì)能夠?yàn)轭愔械某蓡T變量提供前置通知,雖然這可以把通用對(duì)象應(yīng)用到成員變量攔截上,但看起來(lái)Spring并不打算實(shí)現(xiàn)這個(gè)功能。)
public interface MethodBeforeAdvice extends BeforeAdvice { void before(Method m, Object[] args, Object target) throws Throwable; }
注意返回值的類型是void。前置通知可以在連接點(diǎn)執(zhí)行之前插入自定義行為,但是不能修改連接點(diǎn)的返回值。如果一個(gè)前置通知拋出異常,這將中止攔截器鏈的進(jìn)一步執(zhí)行。 異常將沿著攔截器鏈向回傳播。如果異常是非強(qiáng)制檢查的(unchecked)或者已經(jīng)被包含在被調(diào)用方法的簽名中(譯者:即出現(xiàn)在方法聲明的throws子句中),它將被直接返回給客戶端; 否則它將由AOP代理包裝在一個(gè)非強(qiáng)制檢查異常中返回。
這里是Spring里一個(gè)前置通知的例子,它計(jì)算所有方法被調(diào)用的次數(shù):
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; } }
![]() |
Tip |
---|---|
前置通知可以和任何切入點(diǎn)一起使用。 |
如果連接點(diǎn)拋出異常,異常通知(throws advice)將在連接點(diǎn)返回后被調(diào)用。 Spring提供類型檢查的異常通知,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法:它只是一個(gè)標(biāo)記接口用來(lái)標(biāo)識(shí) 所給對(duì)象實(shí)現(xiàn)了一個(gè)或者多個(gè)針對(duì)特定類型的異常通知方法。這些方法應(yīng)當(dāng)滿足下面的格式
afterThrowing([Method], [args], [target], subclassOfThrowable)
只有最后一個(gè)參數(shù)是必須的。因此異常通知方法對(duì)方法及參數(shù)的需求,方法的簽名將從一到四個(gè)參數(shù)之間變化。 下面是一些throws通知的例子。
當(dāng)一個(gè)RemoteException(包括它的子類)被拋出時(shí),下面的通知會(huì)被調(diào)用:
public class RemoteThrowsAdvice implements ThrowsAdvice { public void afterThrowing(RemoteException ex) throws Throwable { // Do something with remote exception } }
當(dāng)一個(gè)ServletException被拋出,下面的通知將被調(diào)用。 和上面的通知不同,它聲明了4個(gè)參數(shù),因此它可以訪問(wèn)被調(diào)用的方法,方法的參數(shù)以及目標(biāo)對(duì)象:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice { public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { // Do something will all arguments } }
最后一個(gè)例子說(shuō)明怎樣在同一個(gè)類里使用兩個(gè)方法來(lái)處理 RemoteException和ServletException。可以在一個(gè)類里組合任意數(shù)量的異常通知方法。
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 will all arguments } }
![]() |
Tip |
---|---|
異常通知可以和任何切入點(diǎn)一起使用。 |
Spring中的一個(gè)后置通知(After Returning advice)必須實(shí)現(xiàn) org.springframework.aop.AfterReturningAdvice 接口,像下面顯示的那樣:
public interface AfterReturningAdvice extends Advice { void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; }
一個(gè)后置通知可以訪問(wèn)返回值(但不能進(jìn)行修改),被調(diào)用方法,方法參數(shù)以及目標(biāo)對(duì)象。
下面的后置通知計(jì)算所有運(yùn)行成功(沒(méi)有拋出異常)的方法調(diào)用:
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; } }
這個(gè)通知不改變執(zhí)行路線。如果通知拋出異常,異常將沿著攔截器鏈返回(拋出)而不是返回被調(diào)用方法的執(zhí)行結(jié)果。
![]() |
Tip |
---|---|
后置通知可以和任何切入點(diǎn)一起使用。 |
Spring 把引入通知(introduction advice)作為一種特殊的攔截通知進(jìn)行處理。
引入通知需要一個(gè)IntroductionAdvisor, 和一個(gè)IntroductionInterceptor, 后者實(shí)現(xiàn)下面的接口:
public interface IntroductionInterceptor extends MethodInterceptor { boolean implementsInterface(Class intf); }
invoke() 方法,繼承了AOP聯(lián)盟MethodInterceptor 接口,必須確保實(shí)現(xiàn)引入: 這里的意思是說(shuō),如果被調(diào)用的方法位于一個(gè)已經(jīng)被引入接口里,這個(gè)引入攔截器將負(fù)責(zé)完成對(duì)這個(gè)方法的調(diào)用--因?yàn)楹笳卟荒苷{(diào)用proceed()方法。
引入通知不能和任何切入點(diǎn)一起使用,因?yàn)樗菓?yīng)用在類級(jí)別而不是方法級(jí)別。 你可以通過(guò)IntroductionAdvisor來(lái)使用引入通知,這個(gè)接口包括下面的方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo { ClassFilter getClassFilter(); void validateInterfaces() throws IllegalArgumentException; } public interface IntroductionInfo { Class[] getInterfaces(); }
這里沒(méi)有MethodMatcher接口,因此也就沒(méi)有 Pointcut接口與引入通知相關(guān)聯(lián)。這里只進(jìn)行類過(guò)濾。
getInterfaces()方法返回這個(gè)advisor所引入的接口。
validateInterfaces()方法將被內(nèi)部使用來(lái)查看被引入的接口是否能夠由配置的IntroductionInterceptor來(lái)實(shí)現(xiàn)。
讓我們看看從Spring測(cè)試集里拿來(lái)的一個(gè)簡(jiǎn)單例子。讓我們假設(shè)我們希望把下面的接口引入給一個(gè)或者多個(gè)對(duì)象:
public interface Lockable { void lock(); void unlock(); boolean locked(); }
這里描述了一個(gè)混合類型。我們希望不論原本對(duì)象是什么類型,都把這個(gè)被通知對(duì)象轉(zhuǎn)換為L(zhǎng)ockable接口并可以調(diào)用lock 和unlock 方法。 如果我們調(diào)用lock() 方法,我們希望所有的setter 方法拋出一個(gè)LockedException異常。這樣我們就可以加入一個(gè)方面來(lái)確保對(duì)象在得到通知之前是不可修改的:一個(gè)關(guān)于AOP的好例子。
首先,我們需要一個(gè)IntroductionInterceptor來(lái)做粗活。這里,我們擴(kuò)展了 org.springframework.aop.support.DelegatingIntroductionInterceptor這個(gè)方便的類。我們能夠直接實(shí)現(xiàn) IntroductionInterceptor接口,但在這個(gè)例子里使用DelegatingIntroductionInterceptor是最好的選擇。
DelegatingIntroductionInterceptor設(shè)計(jì)為把一個(gè)引入托管給一個(gè)實(shí)現(xiàn)這個(gè)接口的類, 這通過(guò)隱藏?cái)r截的使用來(lái)實(shí)現(xiàn)。托管可以被設(shè)置到任何具有構(gòu)造器方法的類;這里使用缺省托管(即使用無(wú)參構(gòu)造器)。 因此在下面這個(gè)例子里,托管者將是DelegatingIntroductionInterceptor的子類 LockMixin。 當(dāng)一個(gè)托管實(shí)現(xiàn)被提供,DelegatingIntroductionInterceptor實(shí)例將查找托管所實(shí)現(xiàn)的所有接口 (除了IntroductionInterceptor之外),并為這些接口的介紹提供支持。子類例如LockMixin 可以調(diào)用suppressInterface(Class intf) 方法來(lái)禁止那些不應(yīng)該被暴露的接口。 然而,不論IntroductionInterceptor支持多少接口, IntroductionAdvisor的使用將控制哪些接口真正被暴露。 一個(gè)被引入的接口將覆蓋目標(biāo)對(duì)象實(shí)現(xiàn)的相同接口.
這樣LockMixin就繼承了DelegatingIntroductionInterceptor并實(shí)現(xiàn)了Lockable 接口本身。 這里父類會(huì)自動(dòng)選擇Lockable接口并提供引入支持,因此我們不需要配置它。用這種方法我們能夠介紹任意數(shù)量的接口。
注意locked實(shí)例變量的用法。這有效地向目標(biāo)對(duì)象增加了額外狀態(tài)。
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里面已經(jīng)包含了一個(gè)實(shí)現(xiàn)--如果一個(gè)方法被引入,這個(gè)實(shí)現(xiàn)將調(diào)用實(shí)際的托管方法,否則它將直接處理連接點(diǎn)--通常這已經(jīng)足夠了。在當(dāng)前這個(gè)例子里,我們需要增加一個(gè)檢查:如果處于加鎖(locked)狀態(tài),沒(méi)有setter方法可以被調(diào)用。
引入處理器的要求是很簡(jiǎn)單的。它的全部要求只是保持一個(gè)特定的LockMixin實(shí)例, 并說(shuō)明被通知的接口--在這個(gè)例子里,只有一個(gè)Lockable接口。 一個(gè)更復(fù)雜的例子也許會(huì)獲取一個(gè)介紹攔截器的引用(后者可以被定義為一個(gè)prototype): 在這種情況下,不需要對(duì)LockMixin進(jìn)行相關(guān)配置,因此我們可以簡(jiǎn)單的用new關(guān)鍵字來(lái)創(chuàng)建它。
public class LockMixinAdvisor extends DefaultIntroductionAdvisor { public LockMixinAdvisor() { super(new LockMixin(), Lockable.class); } }
我們可以很容易應(yīng)用這個(gè)advisor:它不需要配置。(然而,下面是必須記住的:不可以在沒(méi)有IntroductionAdvisor的情況下使用IntroductionInterceptor。) 對(duì)于通常的引入advisor必須是基于實(shí)例的,因?yàn)樗怯袪顟B(tài)的。因此,對(duì)于每個(gè)被通知對(duì)象我們需要一個(gè)不同 實(shí)例的LockMixinAdvisor和LockMixin。這種情況下advisor保存了被通知對(duì)象的部分狀態(tài)。
我們能夠通過(guò)使用Advised.addAdvisor() 的編程方式來(lái)應(yīng)用advisor,或者像其它advisor那樣(也是推薦的方式)在XML里進(jìn)行配置。全部的代理創(chuàng)建選擇(包括“自動(dòng)代理創(chuàng)建器”)將在下面進(jìn)行討論, 看看如何正確地處理introduction和有狀態(tài)混合類型。
在Spring里,一個(gè)advisor是一個(gè)僅僅包含一個(gè)通知對(duì)象和與之關(guān)聯(lián)的切入點(diǎn)表達(dá)式的切面。
除了引入這種特殊形式,任何advisor都可以和任何通知一起工作。 org.springframework.aop.support.DefaultPointcutAdvisor是最常用的advisor類。例如,它可以和: MethodInterceptor,BeforeAdvice 或者 ThrowsAdvice一起使用。
在Spring里有可能在同一個(gè)AOP代理里模糊advisor和通知類型。例如,你可以在一個(gè)代理配置里使用一個(gè)interception環(huán)繞通知,一個(gè)異常通知和一個(gè)前置通知:Spring將負(fù)責(zé)自動(dòng)創(chuàng)建所需的攔截器鏈。
如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來(lái)管理你的業(yè)務(wù)對(duì)象--這正是你應(yīng)該做的--你也許會(huì)想要使用Spring中關(guān)于AOP的FactoryBean。(記住使用工廠bean引入一個(gè)間接層之后,我們就可以創(chuàng)建不同類型的對(duì)象了)。
![]() |
Note |
---|---|
Spring 2.0的AOP支持也在底層使用工廠bean。 |
在Spring里創(chuàng)建一個(gè)AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個(gè)類對(duì)應(yīng)用的切入點(diǎn)和通知提供了完整的控制能力(包括它們的應(yīng)用順序)。然而如果你不需要這種控制,你會(huì)喜歡更簡(jiǎn)單的方式。
像其它的FactoryBean實(shí)現(xiàn)一樣,ProxyFactoryBean引入了一個(gè)間接層。如果你定義一個(gè)名為foo的ProxyFactoryBean, 引用foo的對(duì)象看到的將不是ProxyFactoryBean實(shí)例本身,而是一個(gè)ProxyFactoryBean實(shí)現(xiàn)里getObject() 方法所創(chuàng)建的對(duì)象。 這個(gè)方法將創(chuàng)建一個(gè)AOP代理,它包裝了一個(gè)目標(biāo)對(duì)象。
使用ProxyFactoryBean或者其它IoC相關(guān)類帶來(lái)的最重要的好處之一就是創(chuàng)建AOP代理,這意味著通知和切入點(diǎn)也可以由IoC來(lái)管理。這是一個(gè)強(qiáng)大的功能并使得某些特定的解決方案成為可能, 而這些用其它AOP框架很難做到。例如,一個(gè)通知也許本身也要引用應(yīng)用程序?qū)ο螅ú粌H僅是其它AOP框架中也可以訪問(wèn)的目標(biāo)對(duì)象),這令你可以從依賴注射的可拔插特性中獲益。
通常情況下Spring提供了大多數(shù)的FactoryBean實(shí)現(xiàn),ProxyFactoryBean類本身也是一個(gè)JavaBean。它的屬性被用來(lái):
-
指定你希望代理的目標(biāo)對(duì)象
-
指定是否使用CGLIB(查看下面叫做Section 7.5.3, “基于JDK和CGLIB的代理”的小節(jié))。
一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(lái)(這個(gè)類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:
-
proxyTargetClass:這個(gè)屬性為true時(shí),目標(biāo)類本身被代理而不是目標(biāo)類的接口。如果這個(gè)屬性值被設(shè)為true,CGLIB代理將被創(chuàng)建(可以參看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。
-
optimize:用來(lái)控制通過(guò)CGLIB創(chuàng)建的代理是否使用激進(jìn)的優(yōu)化策略。除非完全了解AOP代理如何處理優(yōu)化,否則不推薦用戶使用這個(gè)設(shè)置。目前這個(gè)屬性僅用于CGLIB代理;對(duì)于JDK動(dòng)態(tài)代理(缺省代理)無(wú)效。
-
frozen:用來(lái)控制代理工廠被配置之后,是否還允許修改通知。缺省值為false(即在代理被配置之后,不允許修改代理的配置)。
-
exposeProxy:決定當(dāng)前代理是否被保存在一個(gè)ThreadLocal中以便被目標(biāo)對(duì)象訪問(wèn)。(目標(biāo)對(duì)象本身可以通過(guò)MethodInvocation來(lái)訪問(wèn),因此不需要ThreadLocal。) 如果個(gè)目標(biāo)對(duì)象需要獲取代理而且exposeProxy屬性被設(shè)為true,目標(biāo)對(duì)象可以使用AopContext.currentProxy()方法。
-
aopProxyFactory:使用AopProxyFactory的實(shí)現(xiàn)。這提供了一種方法來(lái)自定義是否使用動(dòng)態(tài)代理,CGLIB或其它代理策略。 缺省實(shí)現(xiàn)將根據(jù)情況選擇動(dòng)態(tài)代理或者CGLIB。一般情況下應(yīng)該沒(méi)有使用這個(gè)屬性的需要;它是被設(shè)計(jì)來(lái)在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean中需要說(shuō)明的其它屬性包括:
-
proxyInterfaces:需要代理的接口名的字符串?dāng)?shù)組。如果沒(méi)有提供,將為目標(biāo)類使用一個(gè)CGLIB代理(也可以查看下面名為Section 7.5.3, “基于JDK和CGLIB的代理”的章節(jié))。
-
interceptorNames:Advisor的字符串?dāng)?shù)組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優(yōu)先服務(wù)。就是說(shuō)列表里的第一個(gè)攔截器將能夠第一個(gè)攔截調(diào)用。
這里的名字是當(dāng)前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因?yàn)檫@會(huì)導(dǎo)致ProxyFactoryBean忽略通知的單例設(shè)置。
你可以把一個(gè)攔截器的名字加上一個(gè)星號(hào)作為后綴(*)。這將導(dǎo)致這個(gè)應(yīng)用程序里所有名字以星號(hào)之前部分開(kāi)頭的advisor都被應(yīng)用。你可以在Section 7.5.6, “使用“全局”advisor” 發(fā)現(xiàn)一個(gè)使用這個(gè)特性的例子。
-
單例:工廠是否應(yīng)該返回同一個(gè)對(duì)象,不論方法getObject()被調(diào)用的多頻繁。多個(gè)FactoryBean實(shí)現(xiàn)都提供了這個(gè)方法。缺省值是true。如果你希望使用有狀態(tài)的通知--例如,有狀態(tài)的mixin--可以把單例屬性的值設(shè)置為false來(lái)使用原型通知。
這個(gè)小節(jié)作為說(shuō)明性文檔,解釋了對(duì)于一個(gè)目標(biāo)對(duì)象(需要被代理的對(duì)象),ProxyFactryBean是如何決定究竟創(chuàng)建一個(gè)基于JDK還是CGLIB的代理的。
![]() |
Note |
---|---|
ProxyFactoryBean需要?jiǎng)?chuàng)建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。現(xiàn)在ProxyFactoryBean在關(guān)于自動(dòng)檢測(cè)接口方面使用了與TransactionProxyFactoryBean相似的語(yǔ)義。 |
如果一個(gè)需要被代理的目標(biāo)對(duì)象的類(后面將簡(jiǎn)單地稱它為目標(biāo)類)沒(méi)有實(shí)現(xiàn)任何接口,那么一個(gè)基于CGLIB的代理將被創(chuàng)建。這是最簡(jiǎn)單的場(chǎng)景,因?yàn)镴DK代理是基于接口的,沒(méi)有接口意味著沒(méi)有使用JDK進(jìn)行代理的可能。 在目標(biāo)bean里將被插入探測(cè)代碼,通過(guò)interceptorNames屬性給出了攔截器的列表。注意一個(gè)基于CGLIB的代理將被創(chuàng)建即使ProxyFactoryBean的proxyTargetClass屬性被設(shè)置為false。 (很明顯這種情況下對(duì)這個(gè)屬性進(jìn)行設(shè)置是沒(méi)有意義的,最好把它從bean的定義中移除,因?yàn)殡m然這只是個(gè)多余的屬性,但在許多情況下會(huì)引起混淆。)
如果目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口,那么創(chuàng)建代理的類型將根據(jù)ProxyFactoryBean的配置來(lái)決定。
如果ProxyFactoryBean的proxyTargetClass屬性被設(shè)為true,那么一個(gè)基于CGLIB的代理將創(chuàng)建。這樣的規(guī)定是有意義的,遵循了最小驚訝法則(保證了設(shè)定的一致性)。 甚至當(dāng)ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名,而proxyTargetClass屬性被設(shè)置為true仍然將實(shí)際使用基于CGLIB的代理。
如果ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個(gè)或者多個(gè)全限定接口名,一個(gè)基于JDK的代理將被創(chuàng)建。被創(chuàng)建的代理將實(shí)現(xiàn)所有在proxyInterfaces屬性里被說(shuō)明的接口;如果目標(biāo)類實(shí)現(xiàn)了全部在proxyInterfaces屬性里說(shuō)明的接口以及一些額外接口,返回的代理將只實(shí)現(xiàn)說(shuō)明的接口而不會(huì)實(shí)現(xiàn)那些額外接口。
如果ProxyFactoryBean的proxyInterfaces屬性沒(méi)有被設(shè)置,但是目標(biāo)類實(shí)現(xiàn)了一個(gè)(或者更多)接口,那么ProxyFactoryBean將自動(dòng)檢測(cè)到這個(gè)目標(biāo)類已經(jīng)實(shí)現(xiàn)了至少一個(gè)接口, 一個(gè)基于JDK的代理將被創(chuàng)建。被實(shí)際代理的接口將是目標(biāo)類所實(shí)現(xiàn)的全部接口;實(shí)際上,這和在proxyInterfaces屬性中列出目標(biāo)類實(shí)現(xiàn)的每個(gè)接口的情況是一樣的。然而,這將顯著地減少工作量以及輸入錯(cuò)誤的可能性。
讓我們看一個(gè)關(guān)于ProxyFactoryBean的簡(jiǎn)單例子。這個(gè)例子涉及:
-
一個(gè)將被代理的目標(biāo)bean。在下面的例子里這個(gè)bean是“personTarget”。
-
被用來(lái)提供通知的一個(gè)advisor和一個(gè)攔截器。
-
一個(gè)AOP代理bean的定義,它說(shuō)明了目標(biāo)對(duì)象(personTarget bean)以及需要代理的接口,還包括需要被應(yīng)用的通知。
<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 local="personTarget"/></property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
注意interceptorNames屬性接受一組字符串:當(dāng)前工廠中攔截器或advisorbean的名字。攔截器,advisor,前置, 后置和異常通知對(duì)象都可以在這里被使用。這里advisor的順序是很重要的。
![]() |
Note |
---|---|
你也許很奇怪為什么這個(gè)列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設(shè)置為false,它必須返回獨(dú)立的代理實(shí)例。如果任何advisor本身是一個(gè)原型,則每次都返回一個(gè)獨(dú)立實(shí)例,因此它必須能夠從工廠里獲得原型的一個(gè)實(shí)例;保存一個(gè)引用是不夠的。 |
上面“person” bean的定義可以被用來(lái)取代一個(gè)Person接口的實(shí)現(xiàn),就像下面這樣:
Person person = (Person) factory.getBean("person");
在同一個(gè)IoC上下文中其它的bean可以對(duì)這個(gè)bean有基于類型的依賴,就像對(duì)一個(gè)普通的Java對(duì)象那樣:
<bean id="personUser" class="com.mycompany.PersonUser"> <property name="person"><ref local="person" /></property> </bean>
這個(gè)例子里的PersonUser類將暴露一個(gè)類型為Person的屬性。就像我們關(guān)心的那樣,AOP代理可以透明地取代一個(gè)“真實(shí)”的person接口實(shí)現(xiàn)。然而,它的類將是一個(gè)動(dòng)態(tài)代理類。 它可以被轉(zhuǎn)型成Advised接口(將在下面討論)。
就像下面這樣,你可以使用一個(gè)匿名內(nèi)部bean來(lái)隱藏目標(biāo)和代理之間的區(qū)別。僅僅ProxyFactoryBean的定義有所不同;通知的定義只是由于完整性的原因而被包括進(jìn)來(lái):
<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.ProxyFactor Bean"> <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>
對(duì)于只需要一個(gè)Person類型對(duì)象的情況,這是有好處的:如果你希望阻止應(yīng)用程序上下文的用戶獲取一個(gè)指向未通知對(duì)象的引用或者希望避免使用Spring IoC 自動(dòng)織入 時(shí)的混淆。 按理說(shuō)ProxyFactoryBean定義還有一個(gè)優(yōu)點(diǎn)是它是自包含的。然而,有時(shí)能夠從工廠里獲取未通知的目標(biāo)也是一個(gè)優(yōu)點(diǎn):例如,在某些測(cè)試場(chǎng)景里。
如果你需要代理一個(gè)類而不是代理一個(gè)或是更多接口,那么情況將是怎樣?
想象在我們上面的例子里,不存在Person接口:我們需要通知一個(gè)叫做Person的類,它沒(méi)有實(shí)現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動(dòng)態(tài)代理。 這只需簡(jiǎn)單地把上面ProxyFactoryBean的proxyTargetClass屬性設(shè)為true。雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時(shí),通知沒(méi)有實(shí)現(xiàn)接口的類的能力是非常有用的。(通常情況下,Spring沒(méi)有任何規(guī)定。它只是讓你很容易根據(jù)實(shí)際情況選擇最好的解決方案,避免強(qiáng)迫使用特定方式)。
也許你希望你能夠在任何情況下都強(qiáng)制使用CGLIB,甚至在你使用接口的時(shí)候也這樣做。
CGLIB通過(guò)在運(yùn)行時(shí)生成一個(gè)目標(biāo)類的子類來(lái)進(jìn)行代理工作。Spring配置這個(gè)生成的子類對(duì)原始目標(biāo)對(duì)象的方法調(diào)用進(jìn)行托管:子類實(shí)現(xiàn)了裝飾器(Decorator)模式,把通知織入。
CGLIB的代理活動(dòng)應(yīng)當(dāng)對(duì)用戶是透明的。然而,有一些問(wèn)題需要被考慮:
-
Final方法不可以被通知,因?yàn)樗鼈儾荒鼙桓采w。
-
你需要在你的類路徑里有CGLIB 2的庫(kù);使用動(dòng)態(tài)代理的話只需要JDK。
在CGLIB代理和動(dòng)態(tài)代理之間的速度差別是很小的。在Spring 1.0中,動(dòng)態(tài)代理會(huì)快一點(diǎn)點(diǎn)。但這點(diǎn)可能在將來(lái)被改變。這種情況下,選擇使用何種代理時(shí)速度不應(yīng)該成為決定性的理由。
通過(guò)在一個(gè)攔截器名后添加一個(gè)星號(hào),所有bean名字與星號(hào)之前部分相匹配的通知都將被加入到advisor鏈中。這讓你很容易添加一組標(biāo)準(zhǔn)的“全局”advisor:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>globa *</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
你也許需要許多相似的代理定義,特別是定義事務(wù)性代理的時(shí)候。使用父子bean定義,以及內(nèi)部bean定義,可以讓代理定義大大得到極大的簡(jiǎn)化。
首先從父bean開(kāi)始,為代理bean創(chuàng)建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>
這個(gè)bean本身將永遠(yuǎn)不會(huì)被初始化,所以實(shí)際上是不完整的。而后每個(gè)需要?jiǎng)?chuàng)建的代理都是這個(gè)bean定義的子bean定義,它們把代理的目標(biāo)類包裝為一個(gè)內(nèi)部bean定義,因?yàn)槟繕?biāo)對(duì)象本身將不會(huì)被單獨(dú)使用。
<bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean>
當(dāng)然你可以覆蓋從模版中繼承的屬性,例如在下面這個(gè)例子里的事務(wù)傳播設(shè)置:
<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>
要注意上面例子中我們已經(jīng)明確地通過(guò)設(shè)定abstract屬性把父bean定義標(biāo)注為abstract,在前面的章節(jié)里有描述,這樣它實(shí)際上不能被初始化。 缺省情況下應(yīng)用程序上下文(不僅僅是bean工廠)將預(yù)先初始化所有的實(shí)例為單例。因此下面這點(diǎn)是很重要的(至少對(duì)于單例bean來(lái)說(shuō)),如果你有一個(gè)(父)bean定義你希望僅僅作為模版使用,而這個(gè)定義說(shuō)明了一個(gè)類,你必須把abstract參數(shù)設(shè)置為true,否則應(yīng)用程序上下文將試圖預(yù)先初始化它。
使用Spring通過(guò)編程創(chuàng)建AOP代理是很容易的。這使你可以使用Spring AOP而不必依賴于Spring IoC。
下面的清單顯示了如何使用一個(gè)攔截器和一個(gè)advisor來(lái)為一個(gè)目標(biāo)對(duì)象來(lái)創(chuàng)建一個(gè)代理。目標(biāo)對(duì)象實(shí)現(xiàn)的接口將被自動(dòng)代理:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addInterceptor(myMethodInterc ptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
第一步是創(chuàng)建一個(gè)類型為org.springframework.aop.framework.ProxyFactory的對(duì)象。你可以像上面例子里那樣使用一個(gè)目標(biāo)對(duì)象來(lái)創(chuàng)建它,或者在一個(gè)可選的構(gòu)造器里說(shuō)明需要被代理的接口。
你可以添加攔截器或advisor,并在ProxyFactory的生命周期里操作它們。如果你加入一個(gè)IntroductionInterceptionAroundAdvisor,你可以讓代理實(shí)現(xiàn)額外的接口。
在ProxyFactory里也有很方便的方法(繼承自AdvisedSupport)允許你加入其它的通知類型例如前置和異常通知。AdvisedSupport是ProxyFactory 和ProxyFactoryBean的共同父類。
![]() |
Tip |
---|---|
在大多數(shù)應(yīng)用程序里,把AOP代理的創(chuàng)建和IoC框架集成是最佳實(shí)踐。通常情況下我們推薦你在Java代碼外進(jìn)行AOP的配置。 |
在創(chuàng)建了AOP代理之后,你能夠使用org.springframework.aop.framework.Advised接口對(duì)它們進(jìn)行管理。任何AOP代理都能夠被轉(zhuǎn)型為這個(gè)接口,不論它實(shí)現(xiàn)了哪些其它接口。這個(gè)接口包括下面的方法:
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()方法將為每個(gè)已經(jīng)被加入工廠的advisor,攔截器或者其它通知類型返回一個(gè)advisor。如果你曾經(jīng)添加一個(gè)advisor,那么所返回的advisor將是你加入的對(duì)象。 如果你曾經(jīng)加入一個(gè)攔截器或者其它通知類型,Spring將把它們包裝在一個(gè)advisor里,后者使用一個(gè)永遠(yuǎn)返回true的切入點(diǎn)。因此如果你曾經(jīng)加入一個(gè)MethodInterceptor, 返回的advisor將是一個(gè)DefaultPointcutAdvisor,它可以返回你加入的MethodInterceptor和一個(gè)匹配所有類和方法的切入點(diǎn)。
addAdvisor()方法可以用來(lái)添加任何advisor。通常保存切入點(diǎn)和通知的advisor是DefaultPointcutAdvisor,它可以用于任何通知或切入點(diǎn)(但不包括引入類型)。
缺省情況下,你可以加入或移除advisor或者攔截器甚至當(dāng)代理已經(jīng)被創(chuàng)建之后。唯一的限制是無(wú)法加入或者移除一個(gè)引入advisor,因?yàn)楣S中獲得的已有代理不能顯示接口的改變(你可以通過(guò)從工廠里獲取一個(gè)新的代理來(lái)避免這個(gè)問(wèn)題)。
下面是一個(gè)簡(jiǎn)單的例子,它把一個(gè)AOP代理轉(zhuǎn)型為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 us ng a pointcut advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
![]() |
Note |
---|---|
在一個(gè)實(shí)際運(yùn)行的系統(tǒng)里,修改一個(gè)業(yè)務(wù)對(duì)象上的通知是否明智是個(gè)問(wèn)題,雖然無(wú)疑在某些情況下這樣做是合理的。然而這在開(kāi)發(fā)中是很有用的: 例如,在測(cè)試的時(shí)候。對(duì)于希望測(cè)試的方法調(diào)用,有時(shí)我發(fā)現(xiàn)把測(cè)試代碼加入到一個(gè)攔截器或者其它通知里是非常有用的。(例如,通知可以與目標(biāo)方法存在于同一個(gè)事務(wù)里,在把事務(wù)標(biāo)記為回滾之前可以用SQL來(lái)檢查數(shù)據(jù)庫(kù)是否被正確的更新了。) |
依賴于你怎樣創(chuàng)建代理,你通常可以設(shè)置一個(gè) frozen標(biāo)志,在這種情況下 Advised的isFrozen()方法將返回true,任何增加或者移除通知的修改都會(huì)導(dǎo)致一個(gè)AopConfigException異常。 在某些情況下這種凍結(jié)被通知對(duì)象狀態(tài)的能力是很有用的:例如,防止調(diào)用代碼來(lái)移除一個(gè)進(jìn)行安全檢查的攔截器。在Spring 1.1中它也被用來(lái)允許激進(jìn)優(yōu)化,如果已經(jīng)知道不需要運(yùn)行時(shí)對(duì)通知進(jìn)行修改的話。
到目前為止我們已經(jīng)考慮了如何使用ProxyFactoryBean或者類似的工廠bean來(lái)顯式創(chuàng)建AOP代理。
Spring也允許我們使用“自動(dòng)代理”的bean定義,可以自動(dòng)對(duì)被選中的bean定義進(jìn)行代理。這建立在Spring的“bean post processor”功能上,后者允許在容器加載時(shí)修改任何bean的定義。
在這個(gè)模型下,你在你的XML bean定義文件中建立一些特定的bean定義來(lái)配置自動(dòng)代理功能。這允許你僅僅聲明那些將被自動(dòng)代理的適當(dāng)目標(biāo):你不需要使用ProxyFactoryBean。
有兩種方式可以做到這點(diǎn):
-
使用一個(gè)引用當(dāng)前上下文中特定bean的自動(dòng)代理創(chuàng)建器。
-
一個(gè)專用自動(dòng)代理的創(chuàng)建需要被單獨(dú)考慮;自動(dòng)代理創(chuàng)建由源代碼級(jí)別的元數(shù)據(jù)屬性驅(qū)動(dòng)。
org.springframework.aop.framework.autoproxy包提供了下列標(biāo)準(zhǔn)自動(dòng)代理創(chuàng)建器。
BeanNameAutoProxyCreator為名字匹配字符串或者通配符的bean自動(dòng)創(chuàng)建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一樣,這里有一個(gè)interceptorNames屬性而不是一個(gè)攔截器的列表,這允許使用原型(prototype)advisor。這里的“攔截器”可以是advisor或任何通知類型。
與通常的自動(dòng)代理一樣,使用BeanNameAutoProxyCreator的主要目的是把相同的配置一致地應(yīng)用到多個(gè)對(duì)象,并且使用最少量的配置。一個(gè)流行的選擇是把聲明式事務(wù)應(yīng)用到多個(gè)對(duì)象上。
那些名字匹配的Bean定義,例如上面的例子里的“jdkMyBean”和“onlyJdk”,本身只是目標(biāo)類的普通bean定義。一個(gè)AOP對(duì)象將被BeanNameAutoProxyCreator自動(dòng)創(chuàng)建。 相同的通知將被應(yīng)用到全部匹配的bean上。注意如果advisor被使用(而不是像上面例子里那樣使用攔截器),對(duì)于不同bean可以應(yīng)用不同的切入點(diǎn)。
一個(gè)更加通用而且強(qiáng)大得多的自動(dòng)代理創(chuàng)建器是DefaultAdvisorAutoProxyCreator。它自動(dòng)應(yīng)用當(dāng)前上下文中適當(dāng)?shù)腶dvisor,無(wú)需在自動(dòng)代理advisor的bean定義中包括bean的名字。 比起BeanNameAutoProxyCreator,它提供了同樣關(guān)于一致性配置的優(yōu)點(diǎn)而避免了前者的重復(fù)性。
使用這個(gè)功能將涉及:
-
說(shuō)明一個(gè) DefaultAdvisorAutoProxyCreator的bean定義
-
在同一個(gè)或者相關(guān)的上下文中說(shuō)明任意數(shù)量的advisor。注意這些必須是advisor而不僅僅是攔截器或者其它通知。這點(diǎn)是必要的因?yàn)楸仨氂幸粋€(gè)切入點(diǎn)被評(píng)估,以便檢查每個(gè)通知候選bean定義的合適性。
DefaultAdvisorAutoProxyCreator將自動(dòng)評(píng)估包括在每個(gè)advisor中的切入點(diǎn),來(lái)看看它應(yīng)當(dāng)應(yīng)用哪個(gè)(如果有的話)通知到每個(gè)業(yè)務(wù)對(duì)象(例如例子里的“businessObject1”和“businessObject2”)。
這意味著可以向每個(gè)業(yè)務(wù)對(duì)象應(yīng)用任意數(shù)量的advisor。對(duì)于一個(gè)業(yè)務(wù)對(duì)象,如果沒(méi)有任何advisor中的切入點(diǎn)匹配它的任何方法,這個(gè)對(duì)象將不會(huì)被代理。當(dāng)為新的業(yè)務(wù)對(duì)象加入bean定義時(shí),如果有必要它們將自動(dòng)被代理。
通常自動(dòng)代理的好處是它讓調(diào)用者或者被依賴對(duì)象不能得到一個(gè)沒(méi)有通知過(guò)的對(duì)象。在這個(gè)ApplicationContext上調(diào)用getBean("businessObject1")將返回一個(gè)AOP代理,而不是目標(biāo)業(yè)務(wù)對(duì)象。(前面顯示的“內(nèi)部bean”也提供了同樣的優(yōu)點(diǎn)。)
<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"/>
如果你想要把相同的通知一致性地應(yīng)用到許多業(yè)務(wù)對(duì)象上,DefaultAdvisorAutoProxyCreator是非常有用的。一旦框架的定義已經(jīng)完成,你可以簡(jiǎn)單地加入新的業(yè)務(wù)對(duì)象而不必包括特定的代理配置。你也可以很容易的去掉額外的切面--例如,跟蹤或者性能監(jiān)視切面--僅僅對(duì)配置作很小的修改。
DefaultAdvisorAutoProxyCreator支持過(guò)濾(通過(guò)使用一個(gè)命名約定讓只有特定的advisor被評(píng)估,允許在同一個(gè)工廠里使用多個(gè)不同配置的AdvisorAutoProxyCreator)和排序。advisor可以實(shí)現(xiàn)org.springframework.core.Ordered接口來(lái)確保以正確的順序被應(yīng)用。 上面例子里的TransactionAttributeSourceAdvisor 有一個(gè)可配置的序號(hào)值;缺省情況下是沒(méi)有排序的。
一個(gè)非常重要的自動(dòng)代理類型是由元數(shù)據(jù)驅(qū)動(dòng)的。這提供了一種和.NET ServicedComponents相似的編程模型。作為使用類似EJB里的XML描述符的替代,對(duì)于事務(wù)管理和其它企業(yè)服務(wù)的配置都將被保存在源代碼級(jí)別的屬性里。
在這個(gè)情況下,你使用DefaultAdvisorAutoProxyCreator和可以理解元數(shù)據(jù)屬性的advisor。元數(shù)據(jù)被保存在候選advisor里的切入點(diǎn)部分,而不是在自動(dòng)代理創(chuàng)建類本身。
這是一個(gè)DefaultAdvisorAutoProxyCreator的特殊例子,它本身沒(méi)有什么特別。(元數(shù)據(jù)的相關(guān)代碼保存在advisor內(nèi)的切入點(diǎn)里,而不是AOP框架本身)。
JPetStore示例應(yīng)用程序的/attributes 目錄顯示了如何使用參數(shù)驅(qū)動(dòng)的自動(dòng)代理。在這個(gè)例子里,不需要使用TransactionProxyFactoryBean。因?yàn)槭褂昧嗽獢?shù)據(jù)相關(guān)的切入點(diǎn), 所以簡(jiǎn)單在業(yè)務(wù)對(duì)象上定義事務(wù)屬性就足夠了。在/WEB-INF/declarativeServices.xml里的bean定義包括了下面的片斷,注意這是通用的,可以被用在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定義(名字是不重要的,因此甚至可以在定義里省略它)將在當(dāng)前應(yīng)用程序上下文中查找所有合適的切入點(diǎn)。在這個(gè)例子里,TransactionAttributeSourceAdvisor類型的“transactionAdvisor”bean定義將應(yīng)用到帶有一個(gè)事務(wù)屬性的類或方法上。 TransactionAttributeSourceAdvisor的構(gòu)造器依賴于一個(gè)TransactionInterceptor。這個(gè)例子里通過(guò)自動(dòng)織入解決了這個(gè)問(wèn)題。AttributesTransactionAttributeSource依賴于一個(gè)org.springframework.metadata.Attributes接口的實(shí)現(xiàn)。 在這個(gè)代碼片斷里,“attributes”bean使用Jakarta Commons Attributes API來(lái)獲取屬性信息以滿足這個(gè)要求。(應(yīng)用程序代碼必須已經(jīng)使用Commons Attribut來(lái)es的編譯任務(wù)編譯過(guò)了。)
JPetStore示例應(yīng)用程序的 /annotation 目錄包括了一個(gè)由JDK 1.5+注解驅(qū)動(dòng)的自動(dòng)代理的模擬例子。下面的配置允許自動(dòng)檢測(cè)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依賴于一個(gè)PlatformTransactionManager定義,后者沒(méi)有被包括在這個(gè)通用的文件里(雖然它可以被包括在這里)因?yàn)樗趹?yīng)用程序的事務(wù)需求規(guī)范中指定(在這個(gè)例子里使用JTA,而在其它情況下,可以是Hibernate,JDO或者JDBC):
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
![]() |
Tip |
---|---|
如果你只需要聲明式事務(wù)管理,使用這些通用的XML定義將導(dǎo)致Spring自動(dòng)代理所有帶有事務(wù)屬性的類或者方法。你將不需要直接使用AOP工作, 這個(gè)編程模型和.NET的ServicedComponents相似。 |
這個(gè)架構(gòu)是可以擴(kuò)展的。可以在自定義屬性的基礎(chǔ)上進(jìn)行自動(dòng)代理。你所需要做的是:
-
定義你自己的自定義屬性
-
使用必要的通知說(shuō)明一個(gè)advisor,也包括一個(gè)切入點(diǎn),后者可以被類或者方法上的自定義屬性觸發(fā)。你也許能夠使用已有的通知,而僅僅實(shí)現(xiàn)一個(gè)能夠處理自定義屬性的靜態(tài)切入點(diǎn)。
可以讓這些advisor對(duì)于每個(gè)被通知對(duì)象(例如,mixins)都是唯一的:僅僅需要在bean定義中被定義為原型而不是單例。例如,在上面所顯示的Spring測(cè)試集中的LockMixin引入攔截器可以和一個(gè)屬性驅(qū)動(dòng)的切入點(diǎn)聯(lián)合定位一個(gè)mixin, 像這里顯示的這樣。我們使用通用的DefaultPointcutAdvisor,使用JavaBean屬性進(jìn)行配置:
<bean id="lockMixin" class="org.springframework.aop.LockMixin" scope="prototype"/> <bean id="lockableAdvisor" c ass="org.springframework.aop.support.DefaultPointcutAdvisor" scope="prototype"/> <property name="pointcut" ref="myAttributeAwarePointcut"/> <property name="advice" ref="lockMixin"/> </bean> <bean id="anyBean" class="anyclass" ...
如果參數(shù)相關(guān)的切入點(diǎn)匹配anyBean或其它bean定義里的任何方法,mixin將被應(yīng)用。注意lockMixin和lockableAdvisor的定義都是原型。myAttributeAwarePointcut切入點(diǎn)可以是個(gè)單例,因?yàn)樗鼪](méi)有為單個(gè)被通知對(duì)象保持狀態(tài)。
Spring提供了TargetSource的概念,由org.springframework.aop.TargetSource接口進(jìn)行描述。這個(gè)接口負(fù)責(zé)返回一個(gè)實(shí)現(xiàn)連接點(diǎn)的“目標(biāo)對(duì)象(target object)”。每當(dāng)AOP代理處理一個(gè)方法調(diào)用時(shí)都會(huì)向TargetSource的實(shí)現(xiàn)請(qǐng)求一個(gè)目標(biāo)實(shí)例。
使用Spring AOP的開(kāi)發(fā)者通常不需要直接和TargetSource打交道,但這提供了一種強(qiáng)大的方式來(lái)支持池化(pooling),熱交換(hot swappable)和其它高級(jí)目標(biāo)。例如,一個(gè)使用池來(lái)管理實(shí)例的TargetSource可以為每個(gè)調(diào)用返回一個(gè)不同的目標(biāo)實(shí)例。
如果你不指定一個(gè)TargetSource,一個(gè)缺省實(shí)現(xiàn)將被使用,它包裝一個(gè)本地對(duì)象。對(duì)于每次調(diào)用它將返回相同的目標(biāo)(像你期望的那樣)。
讓我們看看Spring提供的標(biāo)準(zhǔn)目標(biāo)源(target source)以及如何使用它們。
![]() |
Tip |
---|---|
當(dāng)使用一個(gè)自定義的目標(biāo)源,你的目標(biāo)通常需要是一個(gè)原型而不是一個(gè)單例的bean定義。這允許Spring在必要時(shí)創(chuàng)建新的目標(biāo)實(shí)例。 |
org.springframework.aop.target.HotSwappableTargetSource允許當(dāng)調(diào)用者保持引用的時(shí)候,切換一個(gè)AOP代理的目標(biāo)。
修改目標(biāo)源的目標(biāo)將立即生效。 HotSwappableTargetSource是線程安全的。
你可以通過(guò)HotSwappableTargetSource的 swap()方法來(lái)改變目標(biāo),就像下面那樣:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget);
所需的XML定義看起來(lái)像下面這樣:
<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()調(diào)用修改了swappable bean的目標(biāo)。保持對(duì)這個(gè)bean的引用的客戶將不知道發(fā)生了這個(gè)修改,但是將可以立即點(diǎn)擊新的目標(biāo)。
這個(gè)例子沒(méi)有添加任何通知--也不必為使用一個(gè)TargetSource添加任何通知--當(dāng)然任何TargetSource都可以與任意通知聯(lián)合使用。
使用一個(gè)池化目標(biāo)源提供了和無(wú)狀態(tài)session EJB類似的編程模型,它維護(hù)一個(gè)包括相同實(shí)例的池,方法調(diào)用結(jié)束后將把對(duì)象釋放回池中。
Spring池化和SLSB池化之間的一個(gè)決定性區(qū)別是Spring池化功能可以用于任何POJO。就像Spring通常情況下那樣,這個(gè)服務(wù)是非侵入式的。
Spring對(duì)Jakarta Commons Pool 1.3提供了開(kāi)箱即用的支持,后者提供了一個(gè)相當(dāng)有效的池化實(shí)現(xiàn)。要使用這個(gè)特性,你需要在應(yīng)用程序路徑中存在commons-pool的Jar文件。 也可以通過(guò)繼承org.springframework.aop.target.AbstractPoolingTargetSource來(lái)支持其它的池化API。
下面是示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype"> ... properties omitted </bean> <bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource"> <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>
注意目標(biāo)對(duì)象--例子里的“businessObjectTarget”--必須是個(gè)原型。這允許PoolingTargetSource的實(shí)現(xiàn)在必要時(shí)為目標(biāo)創(chuàng)建新的實(shí)例來(lái)增大池的容量。查看AbstractPoolingTargetSource和你想要使用的具體子類的Javadoc獲取更多關(guān)于它屬性的信息:maxSize是最基礎(chǔ)的,而且永遠(yuǎn)都要求被提供。
在這個(gè)例子里,“myInterceptor”是一個(gè)攔截器的名字,這個(gè)攔截器需要在同一個(gè)IoC上下文中被定義。然而,定義對(duì)攔截器進(jìn)行池化是不必要的。如果你想要的只是池化而沒(méi)有其它通知,就不要設(shè)置interceptorNames屬性。
可以配置Spring來(lái)把任何被池化對(duì)象轉(zhuǎn)型到org.springframework.aop.target.PoolingConfig接口,這通過(guò)一個(gè)introduction暴露配置以及當(dāng)前池的大小。你需要像這樣定義一個(gè)advisor:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="poolTargetSource"/> <property name="targetMethod" value="getPoolingConfigMixin"/> </bean>
這個(gè)advisor可以通過(guò)調(diào)用AbstractPoolingTargetSource類上的一個(gè)方便的方法來(lái)獲得,因此這里使用MethodInvokingFactoryBean。這個(gè)advisor名(這里是“poolConfigAdvisor”)必須在提供被池化對(duì)象的ProxyFactoryBean里的攔截器名列表里中。
轉(zhuǎn)型看起來(lái)像這樣:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize());
![]() |
Note |
---|---|
池化無(wú)狀態(tài)服務(wù)對(duì)象通常是不必要的。我們不認(rèn)為這(池化)應(yīng)當(dāng)是缺省的選擇,因?yàn)槎鄶?shù)無(wú)狀態(tài)對(duì)象是先天線程安全的,如果資源被緩存,那么對(duì)實(shí)例進(jìn)行池化會(huì)引起很多問(wèn)題。 |
使用自動(dòng)代理時(shí)池化更加簡(jiǎn)單。可以為任何自動(dòng)代理創(chuàng)建器設(shè)置所使用的TargetSource
建立一個(gè)“原型”目標(biāo)源和池化TargetSource很相似。在這個(gè)例子里,當(dāng)每次方法調(diào)用時(shí),將創(chuàng)建一個(gè)目標(biāo)的新實(shí)例。雖然在新版本的JVM中創(chuàng)建一個(gè)新對(duì)象的代價(jià)并不高,但是把新對(duì)象織入(滿足它的IoC依賴)可能是很昂貴的。因此如果沒(méi)有很好的理由,你不應(yīng)該使用這個(gè)方法。
為了做到這點(diǎn),你可以把上面的poolTargetSource定義修改成下面的形式。(為了清楚說(shuō)明,修改了bean的名字。)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean>
這里只有一個(gè)屬性:目標(biāo)bean的名字。TargetSource的實(shí)現(xiàn)使用繼承來(lái)確保命名的一致性。就像池化目標(biāo)源那樣,目標(biāo)bean必須是一個(gè)原型的bean定義。
如果你需要為每個(gè)進(jìn)來(lái)的請(qǐng)求(即每個(gè)線程)創(chuàng)建一個(gè)對(duì)象,ThreadLocal目標(biāo)源是很有用的。 ThreadLocal的概念提供了一個(gè)JDK范圍的功能,這可以為一個(gè)線程透明的保存資源。建立一個(gè) ThreadLocalTargetSource的過(guò)程和其它目標(biāo)源幾乎完全一樣:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> </bean>
![]() |
Note |
---|---|
如果不正確的在一個(gè)多線程和多類加載器的環(huán)境里使用ThreadLocal,將帶來(lái)嚴(yán)重的問(wèn)題(可能潛在地導(dǎo)致內(nèi)存泄漏)。永遠(yuǎn)記住應(yīng)該把一個(gè)threadlocal包裝在其它的類里,并永遠(yuǎn)不要直接使用ThreadLocal本身(當(dāng)然是除了threadlocal包裝類之外)。 同時(shí),永遠(yuǎn)記住正確的設(shè)置(set)和取消(unset)(后者僅僅需要調(diào)用ThreadLocal.set(null))綁定到線程的本地資源。取消在任何情況下都應(yīng)該進(jìn)行,否則也許會(huì)導(dǎo)致錯(cuò)誤的行為。Spring的ThreadLocal支持將為你處理這個(gè)問(wèn)題,所以如果沒(méi)有其它正確的處理代碼,永遠(yuǎn)應(yīng)該考慮使用這個(gè)功能。 |
Spring AOP被設(shè)計(jì)為可擴(kuò)展的。通過(guò)在內(nèi)部使用攔截實(shí)現(xiàn)策略,你可以支持已有的環(huán)繞通知,前置通知,異常通知和后置通知之外的任意通知類型,它是開(kāi)箱即用的。
org.springframework.aop.framework.adapter包是一個(gè)SPI包,它允許添加新的自定義通知類型而無(wú)需修改核心框架。對(duì)于自定義Advice類型的唯一的限制是它必須實(shí)現(xiàn)org.aopalliance.aop.Advice這個(gè)標(biāo)記接口。
請(qǐng)參考org.springframework.aop.framework.adapter包的Javadoc來(lái)了解進(jìn)一步的信息。
posted on 2007-09-07 09:19 劉錚 閱讀(697) 評(píng)論(0) 編輯 收藏 所屬分類: Spring