?
? ????? PHP ??? ???? ??? ?? ??
如果你無(wú)法使用Java 5,或者你比較喜歡使用XML格式,Spring2.0也提供了使用新的"aop"命名空間來(lái)定義一個(gè)切面。 和使用@AspectJ風(fēng)格完全一樣,切入點(diǎn)表達(dá)式和通知類型同樣得到了支持,因此在這一節(jié)中我們將著重介紹新的 語(yǔ)法并回顧前一節(jié)(第?6.2?節(jié) “@AspectJ支持”)對(duì)編寫一個(gè)切入點(diǎn)表達(dá)式和綁定通知參數(shù)的討論。
使用本章所介紹的aop命名空間標(biāo)簽,你需要引入附錄?A, XML Schema-based configuration
中提及的spring-aop schema。
參見(jiàn)第?A.2.7?節(jié) “The aop
schema”了解如何在aop命名空間中引入標(biāo)簽。
在Spring的配置文件中,所有的切面和通知都必須定義在<aop:config>
元素內(nèi)部。
(一個(gè)application context可以包含多個(gè) <aop:config>
)。
一個(gè)<aop:config>
可以包含pointcut,advisor和aspect元素
(注意這三個(gè)元素必須按照這個(gè)順序進(jìn)行聲明)。
<aop:config>
風(fēng)格的配置使得Spring
auto-proxying機(jī)制的使用變得很笨重。如果你已經(jīng)通過(guò)
BeanNameAutoProxyCreator
或類似的東西顯式使用auto-proxying,它可能會(huì)導(dǎo)致問(wèn)題
(例如通知沒(méi)有被織入)。 推薦的使用模式是僅僅使用<aop:config>
風(fēng)格,
或者僅僅使用AutoProxyCreator
風(fēng)格。
有了schema的支持,切面就和常規(guī)的Java對(duì)象一樣被定義成application context中的一個(gè)bean。 對(duì)象的字段和方法提供了狀態(tài)和行為信息,XML文件則提供了切入點(diǎn)和通知信息。
切面使用<aop:aspect>來(lái)聲明,backing bean(支持bean)通過(guò) ref
屬性來(lái)引用:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
切面的支持bean(上例中的"aBean
")可以象其他Spring bean一樣被容器管理配置以及依賴注入。
一個(gè)命名切入點(diǎn)可以在<aop:config>元素中定義,這樣多個(gè)切面和通知就可以共享該切入點(diǎn)。
一個(gè)描述service層中所有service執(zhí)行的切入點(diǎn)可以定義如下:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> </aop:config>
注意切入點(diǎn)表達(dá)式本身使用了與第?6.2?節(jié) “@AspectJ支持”中描述的相同的AspectJ切入點(diǎn)表達(dá)式語(yǔ)言。 如果你在Java 5環(huán)境下使用基于schema的聲明風(fēng)格,可參考切入點(diǎn)表達(dá)式類型(@Aspects)中定義的命名切入點(diǎn), 不過(guò)這個(gè)特性在JDK1.4及以下版本中是不可用的(因?yàn)橐蕾囉贘ava 5中的AspectJ反射API)。所以在JDK 1.5中, 上面的切入點(diǎn)的另外一種定義形式如下:
<aop:config> <aop:pointcut id="businessService" expression="com.xyz.myapp.SystemArchitecture.businessService()"/> </aop:config>
假定你有一個(gè)在第?6.2.3.3?節(jié) “共享通用切入點(diǎn)定義”中
描述的SystemArchitecture
切面。
在切面里面聲明一個(gè)切入點(diǎn)和聲明一個(gè)頂級(jí)的切入點(diǎn)非常類似:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> ... </aop:aspect> </aop:config>
幾乎和@AspectJ切面中的一樣,使用基于schema定義風(fēng)格聲明的切入點(diǎn)可以收集(collect) 連接點(diǎn)上下文。例如,下面的切入點(diǎn)收集'this'對(duì)象作為連接點(diǎn)上下文并傳遞它給通知:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/> <aop:before pointcut-ref="businessService" method="monitor"/> ... </aop:aspect> </aop:config>
通過(guò)包含匹配名字的參數(shù),通知被聲明來(lái)接收收集的連接點(diǎn)上下文:
public void monitor(Object service) { ... }
當(dāng)需要連接子表達(dá)式的時(shí)候,'&&'在XML中用起來(lái)非常不方便,所以關(guān)鍵字'and', 'or' 和 'not'可以分別用來(lái)代替'&&', '||' 和 '!'。例如,上面切入點(diǎn)更好的寫法如下:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
注意這種方式定義的切入點(diǎn)通過(guò)XML id來(lái)查找,并且不能定義切入點(diǎn)參數(shù)。在基于schema的定義風(fēng)格中 命名切入點(diǎn)支持較之@AspectJ風(fēng)格受到了很多的限制。
和@AspectJ風(fēng)格一樣,基于schema的風(fēng)格也支持5種通知類型并且兩者具有同樣的語(yǔ)義。
前置通知在匹配方法執(zhí)行前運(yùn)行。在<aop:aspect>
中使用<aop:before>
元素來(lái)聲明它。
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
這里dataAccessOperation
是一個(gè)頂級(jí)(<aop:config>
)切入點(diǎn)的id。
而要定義內(nèi)置切入點(diǎn),需將pointcut-ref
屬性替換為pointcut
屬性:
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut="execution(* com.xyz.myapp.dao.*.*(..))" method="doAccessCheck"/> ... </aop:aspect>
正如我們?cè)贎AspectJ風(fēng)格章節(jié)中討論過(guò)的,使用命名切入點(diǎn)能夠明顯的提高代碼的可讀性。
Method屬性標(biāo)識(shí)了提供通知主體的方法(doAccessCheck
)。
這個(gè)方法必須定義在包含通知的切面元素所引用的bean中。在一個(gè)數(shù)據(jù)訪問(wèn)操作執(zhí)行之前
(一個(gè)方法執(zhí)行由切入點(diǎn)表達(dá)式所匹配的連接點(diǎn)),切面中的"doAccessCheck"會(huì)被調(diào)用。
后置通知在匹配的方法完全執(zhí)行后運(yùn)行。和前置通知一樣,可以在<aop:aspect>
里面聲明它。例如:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
和@AspectJ風(fēng)格一樣,通知主體可以得到返回值。使用returning屬性來(lái)指定傳遞返回值的參數(shù)名:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> ... </aop:aspect>
doAccessCheck方法必須聲明一個(gè)名字叫 retVal
的參數(shù)。
參數(shù)的類型依照@AfterReturning所描述的方法強(qiáng)制匹配。例如,方法簽名可以這樣聲明:
public void doAccessCheck(Object retVal) {...
異常通知在匹配方法拋出異常退出時(shí)執(zhí)行。在<aop:aspect>
中使用
after-throwing元素來(lái)聲明:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" method="doRecoveryActions"/> ... </aop:aspect>
和@AspectJ風(fēng)格一樣,通知主體可以得到拋出的異常。使用throwing屬性來(lái)指定傳遞異常的參數(shù)名:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" throwing="dataAccessEx" method="doRecoveryActions"/> ... </aop:aspect>
doRecoveryActions方法必須聲明一個(gè)名字為dataAccessEx
的參數(shù)。
參數(shù)的類型依照@AfterThrowing所描述的方法強(qiáng)制匹配。例如:方法簽名可以如下這般聲明:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
最終通知無(wú)論如何都會(huì)在匹配方法退出后執(zhí)行。使用after
元素來(lái)聲明它:
<aop:aspect id="afterFinallyExample" ref="aBean"> <aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/> ... </aop:aspect>
環(huán)繞通知是最后一種通知類型。環(huán)繞通知在匹配方法運(yùn)行期的“周圍”執(zhí)行。 它有機(jī)會(huì)在目標(biāo)方法的前面和后面執(zhí)行,并決定什么時(shí)候運(yùn)行,怎么運(yùn)行,甚至是否運(yùn)行。 環(huán)繞通知經(jīng)常在需要在一個(gè)方法執(zhí)行前后共享狀態(tài)信息,并且是在線程安全的情況下使用 (啟動(dòng)和停止一個(gè)計(jì)時(shí)器就是一個(gè)例子)。注意選擇能滿足你需求的最簡(jiǎn)單的通知類型; 如果簡(jiǎn)單的前置通知能做的事情就絕對(duì)不要使用環(huán)繞通知。
Around通知使用aop:around
元素來(lái)聲明。通知方法的第一個(gè)參數(shù)的類型必須是
ProceedingJoinPoint
類型。在通知的主體中,調(diào)用
ProceedingJoinPoint
的proceed()
方法來(lái)執(zhí)行真正的方法。
proceed
方法也可能會(huì)被調(diào)用并且傳入一個(gè)Object[]
對(duì)象 -
該數(shù)組將作為方法執(zhí)行時(shí)候的參數(shù)。參見(jiàn)第?6.2.4.5?節(jié) “環(huán)繞通知”中調(diào)用具有
Object[]
的proceed方法。
<aop:aspect id="aroundExample" ref="aBean"> <aop:around pointcut-ref="businessService" method="doBasicProfiling"/> ... </aop:aspect>
doBasicProfiling
通知的實(shí)現(xiàn)和@AspectJ中的例子完全一樣(當(dāng)然要去掉注解):
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
Schema-based聲明風(fēng)格和@AspectJ一樣,支持多種類型的通知:通過(guò)通知方法參數(shù)名字來(lái)匹配切入點(diǎn)參數(shù)。
參見(jiàn)第?6.2.4.6?節(jié) “通知參數(shù)(Advice parameters)”獲取詳細(xì)信息。如果你希望顯式指定通知方法的參數(shù)名
(而不是依靠先前提及的偵測(cè)策略),可以通過(guò)通知元素的arg-names
屬性來(lái)實(shí)現(xiàn),它的處理和
在第?6.2.4.6.3?節(jié) “確定參數(shù)名”中所描述的對(duì)通知注解中"argNames"屬性的處理方式一樣。
示例如下:
<aop:before pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)" method="audit" arg-names="auditable"/>
arg-names
屬性接受由逗號(hào)分割的參數(shù)名列表。
下面是個(gè)稍微復(fù)雜的基于XSD的例子,它展示了關(guān)聯(lián)了多個(gè)強(qiáng)類型參數(shù)的環(huán)繞通知的使用。
package x.y.service; public interface FooService { Foo getFoo(String fooName, int age); } public class DefaultFooService implements FooService { public Foo getFoo(String name, int age) { return new Foo(name, age); } }
接下來(lái)看切面。注意profile(..)
方法接受多個(gè)強(qiáng)類型參數(shù),
首先連接點(diǎn)在方法調(diào)用時(shí)執(zhí)行,這個(gè)參數(shù)指明profile(..)
會(huì)被用作
環(huán)繞
通知:
package x.y; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; public class SimpleProfiler { public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable { StopWatch clock = new StopWatch( "Profiling for '" + name + "' and '" + age + "'"); try { clock.start(call.toShortString()); return call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } } }
最后這里是使得上面的通知針對(duì)一個(gè)特定連接點(diǎn)而執(zhí)行所必需的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- this is the object that will be proxied by Spring's AOP infrastructure --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this is the actual advice itself --> <bean id="profiler" class="x.y.SimpleProfiler"/> <aop:config> <aop:aspect ref="profiler"> <aop:pointcut id="theExecutionOfSomeFooServiceMethod" expression="execution(* x.y.service.FooService.getFoo(String,int)) and args(name, age)"/> <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod" method="profile"/> </aop:aspect> </aop:config> </beans>
如果我們有下面的驅(qū)動(dòng)腳本,我們將在標(biāo)準(zhǔn)輸出上得到如下的輸出:
import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import x.y.service.FooService; public final class Boot { public static void main(final String[] args) throws Exception { BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml"); FooService foo = (FooService) ctx.getBean("fooService"); foo.getFoo("Pengo", 12); } }
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
引入(在AspectJ中稱為inter-type聲明)允許一個(gè)切面聲明一個(gè)通知對(duì)象實(shí)現(xiàn)指定接口, 并且提供了一個(gè)接口實(shí)現(xiàn)類來(lái)代表這些對(duì)象。
引入的定義使用aop:aspect
中的aop:declare-parents
元素。
該元素用于聲明所匹配的類型有一個(gè)新的父類型(所以有了這個(gè)名字)。
例如,給定接口UsageTracked
,
以及這個(gè)接口的一個(gè)實(shí)現(xiàn)類 DefaultUsageTracked
,
下面的切面聲明所有實(shí)現(xiàn)service接口的類同時(shí)實(shí)現(xiàn) UsageTracked
接口。(比如為了通過(guò)JMX輸出統(tǒng)計(jì)信息)
<aop:aspect id="usageTrackerAspect" ref="usageTracking"> <aop:declare-parents types-matching="com.xzy.myapp.service.*+" implement-interface="com.xyz.myapp.service.tracking.UsageTracked" default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/> <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)" method="recordUsage"/> </aop:aspect>
usageTracking
bean的支持類可以包含下面的方法:
public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); }
要實(shí)現(xiàn)的接口由implement-interface
屬性來(lái)指定。
types-matching
屬性的值是一個(gè)AspectJ類型模式:任何匹配類型的bean都會(huì)實(shí)現(xiàn)
UsageTracked
接口。注意在上面前置通知的例子中,
serevice bean可以直接用作UsageTracked
接口的實(shí)現(xiàn)。
如果以編程形式訪問(wèn)一個(gè)bean,你可以這樣來(lái)寫:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
采用Schema風(fēng)格來(lái)定義切面僅支持一種實(shí)例化模型就是singlton模型。其他的實(shí)例化模型或許以后的版本會(huì)支持。
"advisor"這個(gè)概念來(lái)自Spring1.2對(duì)AOP的支持,而在AspectJ中沒(méi)有等價(jià)的概念。 advisor就像一個(gè)小的自包含的切面,這個(gè)切面只有一個(gè)通知。切面自身通過(guò)一個(gè)bean表示, 并且必須實(shí)現(xiàn)一個(gè)在第?7.3.2?節(jié) “Spring里的通知類型”中描述的通知接口。 Advisor可以很好的利用AspectJ的切入點(diǎn)表達(dá)式。
Spring 2.0通過(guò)<aop:advisor>
元素來(lái)支持advisor概念。
你將會(huì)發(fā)現(xiàn)大多數(shù)情況下它會(huì)和transactional advice一起使用,在Spring 2.0中它有自己的命名空間。其格式如下:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
和上面所使用的pointcut-ref
屬性一樣,你還可以使用pointcut
屬性來(lái)定義一個(gè)內(nèi)聯(lián)的切入點(diǎn)表達(dá)式。
為了定義一個(gè)advisor的優(yōu)先級(jí)以便讓通知具有次序,使用order
屬性來(lái)定義advisor中
Ordered
的值 。
讓我們看看第?6.2.7?節(jié) “例子”中并發(fā)鎖失敗重試的例子, 當(dāng)使用schema重寫它時(shí)是什么樣子。
因?yàn)椴l(fā)的問(wèn)題,有時(shí)候business services可能會(huì)失?。ɡ?,死鎖失?。?。如果重試操作,下一次很可能就會(huì)成功。
對(duì)于business services來(lái)說(shuō),這種情況下重試是很正常的(Idempotent操作不需要用戶參與,否則會(huì)得出矛盾的結(jié)論)
我們可能需要透明的重試操作以避免客戶看到一個(gè)OptimisticLockingFailureException
異常。很明顯,在一個(gè)橫切多層的情況下,這是非常有必要的,因此通過(guò)切面來(lái)實(shí)現(xiàn)是很理想的。
因?yàn)橄胍卦嚥僮?,我們需要使用環(huán)繞通知,這樣就可以多次調(diào)用proceed()方法。 下面是簡(jiǎn)單的切面實(shí)現(xiàn)(只是一個(gè)schema支持的普通Java 類):
public class ConcurrentOperationExecutor implements Ordered { private static final int DEFAULT_MAX_RETRIES = 2; private int maxRetries = DEFAULT_MAX_RETRIES; private int order = 1; public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; PessimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(PessimisticLockingFailureException ex) { lockFailureException = ex; } } while(numAttempts <= this.maxRetries); throw lockFailureException; } }
請(qǐng)注意切面實(shí)現(xiàn)了Ordered
接口,這樣我們就可以把切面的優(yōu)先級(jí)設(shè)定為
高于事務(wù)通知(我們每次重試的時(shí)候都想要在一個(gè)全新的事務(wù)中進(jìn)行)。
maxRetries
和 order
屬性都可以在Spring中配置。
主要的動(dòng)作在doConcurrentOperation
這個(gè)環(huán)繞通知方法中發(fā)生。
我們首先會(huì)嘗試處理,如果得到一個(gè)OptimisticLockingFailureException
異常,我們僅僅重試直到耗盡所有預(yù)設(shè)的重試次數(shù)。
這個(gè)類跟我們?cè)贎AspectJ的例子中使用的是相同的,只是沒(méi)有使用注解。
對(duì)應(yīng)的Spring配置如下:
<aop:config> <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor"> <aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:around pointcut-ref="idempotentOperation" method="doConcurrentOperation"/> </aop:aspect> </aop:config> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
請(qǐng)注意我們現(xiàn)在假設(shè)所有的bussiness services都是idempotent。如果不是這樣,我們可以改寫切面,
通過(guò)引入一個(gè)Idempotent
注解,讓它只調(diào)用idempotent:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
并且對(duì)service操作的實(shí)現(xiàn)進(jìn)行注解。這時(shí)如果你只希望改變切面重試idempotent操作,
你只需要改寫切入點(diǎn)表達(dá)式,讓其只匹配@Idempotent
操作:
<aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..)) and @annotation(com.xyz.myapp.service.Idempotent)"/>