?
本文檔使用 php中文網(wǎng)手冊(cè) 發(fā)布
@AspectJ使用了Java 5的注解,可以將切面聲明為普通的Java類(lèi)。@AspectJ樣式在AspectJ 5發(fā)布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一樣的注解,并使用AspectJ來(lái)做切入點(diǎn)解析和匹配。但是,AOP在運(yùn)行時(shí)仍舊是純的Spring AOP,并不依賴(lài)于AspectJ的編譯器或者織入器(weaver)。
使用AspectJ的編譯器或者織入器的話就可以使用完整的AspectJ語(yǔ)言,我們將在第?6.8?節(jié) “在Spring應(yīng)用中使用AspectJ”中討論這個(gè)問(wèn)題。
為了在Spring配置中使用@AspectJ切面,你首先必須啟用Spring對(duì)@AspectJ切面配置的支持,并確保自動(dòng)代理(autoproxying)的bean是否能被這些切面通知。自動(dòng)代理是指Spring會(huì)判斷一個(gè)bean是否使用了一個(gè)或多個(gè)切面通知,并據(jù)此自動(dòng)生成相應(yīng)的代理以攔截其方法調(diào)用,并且確保通知在需要時(shí)執(zhí)行。
通過(guò)在你的Spring的配置中引入下列元素來(lái)啟用Spring對(duì)@AspectJ的支持:
<aop:aspectj-autoproxy/>
我們假定你正在使用附錄?A, XML Schema-based configuration
所描述的schema支持。關(guān)于如何在aop的命名空間中引入這些標(biāo)簽,請(qǐng)參見(jiàn)第?A.2.7?節(jié) “The aop
schema”
如果你正在使用DTD,你仍然可以通過(guò)在你的application context中添加如下定義來(lái)啟用@AspectJ支持:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
你需要在你的應(yīng)用程序的classpath中引入兩個(gè)AspectJ庫(kù):aspectjweaver.jar
和aspectjrt.jar
。這些庫(kù)可以在AspectJ的安裝包(1.5.1或者之后的版本)的'lib'
目錄里找到,或者也可以在Spring-with-dependencies發(fā)布包的'lib/aspectj'
目錄下找到。
啟用@AspectJ支持后,在application context中定義的任意帶有一個(gè)@Aspect切面(擁有@Aspect
注解)的bean都將被Spring自動(dòng)識(shí)別并用于配置Spring AOP。以下例子展示了為完成一個(gè)不是非常有用的切面所需要的最小定義:
application context中一個(gè)常見(jiàn)的bean定義,它指向一個(gè)使用了@Aspect
注解的bean類(lèi):
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
以及NotVeryUsefulAspect
類(lèi)的定義,使用了
org.aspectj.lang.annotation.Aspect
注解。
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
切面(用@Aspect
注解的類(lèi))和其他類(lèi)一樣有方法和字段定義。他們也可能包括切入點(diǎn),通知和引入(inter-type)聲明。
在Spring AOP中,擁有切面的類(lèi)本身不可能是其它切面中通知的目標(biāo)。一個(gè)類(lèi)上面的@Aspect注解標(biāo)識(shí)它為一個(gè)切面,并且從自動(dòng)代理中排除它。
在前面我們提到,切入點(diǎn)決定了連接點(diǎn)關(guān)注的內(nèi)容,使得我們可以控制通知什么時(shí)候執(zhí)行。Spring AOP只支持Spring bean的方法執(zhí)行連接點(diǎn)。所以你可以把切入點(diǎn)看做是Spring bean上方法執(zhí)行的匹配。一個(gè)切入點(diǎn)聲明有兩個(gè)部分:一個(gè)包含名字和任意參數(shù)的簽名,還有一個(gè)切入點(diǎn)表達(dá)式,該表達(dá)式?jīng)Q定了我們關(guān)注那個(gè)方法的執(zhí)行。在@AspectJ注解風(fēng)格的AOP中,一個(gè)切入點(diǎn)簽名通過(guò)一個(gè)普通的方法定義來(lái)提供,并且切入點(diǎn)表達(dá)式使用@Pointcut
注解來(lái)表示(作為切入點(diǎn)簽名的方法必須返回void
類(lèi)型)。
用一個(gè)例子能幫我們清楚的區(qū)分切入點(diǎn)簽名和切入點(diǎn)表達(dá)式之間的差別,下面的例子定義了一個(gè)切入點(diǎn)'anyOldTransfer'
,這個(gè)切入點(diǎn)將匹配任何名為 "transfer" 的方法的執(zhí)行:
@Pointcut("execution(* transfer(..))")// the pointcut expression private void anyOldTransfer() {}// the pointcut signature
切入點(diǎn)表達(dá)式,也就是組成@Pointcut
注解的值,是正規(guī)的AspectJ 5切入點(diǎn)表達(dá)式。如果你想要更多了解AspectJ的切入點(diǎn)語(yǔ)言,請(qǐng)參見(jiàn)AspectJ編程指南(如果要了解基于Java 5的擴(kuò)展請(qǐng)參閱AspectJ 5 開(kāi)發(fā)手冊(cè))或者其他人寫(xiě)的關(guān)于AspectJ的書(shū),例如Colyer et. al.著的“Eclipse AspectJ”或者Ramnivas Laddad著的“AspectJ in Action”。
Spring AOP支持在切入點(diǎn)表達(dá)式中使用如下的AspectJ切入點(diǎn)指示符:
execution - 匹配方法執(zhí)行的連接點(diǎn),這是你將會(huì)用到的Spring的最主要的切入點(diǎn)指示符。
within - 限定匹配特定類(lèi)型的連接點(diǎn)(在使用Spring AOP的時(shí)候,在匹配的類(lèi)型中定義的方法的執(zhí)行)。
this - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中bean reference(Spring AOP 代理)是指定類(lèi)型的實(shí)例。
target - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中目標(biāo)對(duì)象(被代理的應(yīng)用對(duì)象)是指定類(lèi)型的實(shí)例。
args - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中參數(shù)是指定類(lèi)型的實(shí)例。
@target
- 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中正執(zhí)行對(duì)象的類(lèi)持有指定類(lèi)型的注解。
@args
- 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中實(shí)際傳入?yún)?shù)的運(yùn)行時(shí)類(lèi)型持有指定類(lèi)型的注解。
@within
- 限定匹配特定的連接點(diǎn),其中連接點(diǎn)所在類(lèi)型已指定注解(在使用Spring AOP的時(shí)候,所執(zhí)行的方法所在類(lèi)型已指定注解)。
@annotation - 限定匹配特定的連接點(diǎn)(使用Spring AOP的時(shí)候方法的執(zhí)行),其中連接點(diǎn)的主題持有指定的注解。
另外,Spring AOP還提供了一個(gè)名為'bean
'的PCD。這個(gè)PCD允許你限定匹配連接點(diǎn)到一個(gè)特定名稱(chēng)的Spring bean,或者到一個(gè)特定名稱(chēng)Spring bean的集合(當(dāng)使用通配符時(shí))。'bean
' PCD具有下列的格式:
bean(idOrNameOfBean)
'idOrNameOfBean
'標(biāo)記可以是任何Spring bean的名字:限定通配符使用'*
'來(lái)提供,如果你為Spring bean制定一些命名約定,你可以非常容易地編寫(xiě)一個(gè)'bean
' PCD表達(dá)式將它們選出來(lái)。和其它連接點(diǎn)指示符一樣,'bean
' PCD也支持&&, ||和 !邏輯操作符。
請(qǐng)注意'bean
' PCD僅僅 被Spring AOP支持而不是AspectJ. 這是Spring對(duì)AspectJ中定義的標(biāo)準(zhǔn)PCD的一個(gè)特定擴(kuò)展。
'bean
' PCD不僅僅可以在類(lèi)型級(jí)別(被限制在基于織入AOP上)上操作而還可以在實(shí)例級(jí)別(基于Spring bean的概念)上操作。
因?yàn)镾pring AOP限制了連接點(diǎn)必須是方法執(zhí)行級(jí)別的,上文pointcut指示符中的討論也給出了一個(gè)定義,這個(gè)定義和AspectJ的編程指南中的定義相比顯得更加狹窄。除此之外,AspectJ它本身有基于類(lèi)型的語(yǔ)義,在執(zhí)行的連接點(diǎn)'this'和'target'都是指同一個(gè)對(duì)象,也就是執(zhí)行方法的對(duì)象。Spring AOP是一個(gè)基于代理的系統(tǒng),并且嚴(yán)格區(qū)分代理對(duì)象本身(對(duì)應(yīng)于'this')和背后的目標(biāo)對(duì)象(對(duì)應(yīng)于'target')
切入點(diǎn)表達(dá)式可以使用'&', '||' 和 '!'來(lái)組合。還可以通過(guò)名字來(lái)指向切入點(diǎn)表達(dá)式。以下的例子展示了三種切入點(diǎn)表達(dá)式: anyPublicOperation
(在一個(gè)方法執(zhí)行連接點(diǎn)代表了任意public方法的執(zhí)行時(shí)匹配);inTrading
(在一個(gè)代表了在交易模塊中的任意的方法執(zhí)行時(shí)匹配)和 tradingOperation
(在一個(gè)代表了在交易模塊中的任意的公共方法執(zhí)行時(shí)匹配)。
@Pointcut("execution(public * *(..))") private void anyPublicOperation() {} @Pointcut("within(com.xyz.someapp.trading..*") private void inTrading() {} @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {}
如上所示,用更少的命名組件來(lái)構(gòu)建更加復(fù)雜的切入點(diǎn)表達(dá)式是一種最佳實(shí)踐。當(dāng)用名字來(lái)指定切入點(diǎn)時(shí)使用的是常見(jiàn)的Java成員可視性訪問(wèn)規(guī)則。(比如說(shuō),你可以在同一類(lèi)型中訪問(wèn)私有的切入點(diǎn),在繼承關(guān)系中訪問(wèn)受保護(hù)的切入點(diǎn),可以在任意地方訪問(wèn)公共切入點(diǎn))。成員可視性訪問(wèn)規(guī)則不影響到切入點(diǎn)的匹配。
當(dāng)開(kāi)發(fā)企業(yè)級(jí)應(yīng)用的時(shí)候,你通常會(huì)想要從幾個(gè)切面來(lái)引用模塊化的應(yīng)用和特定操作的集合。我們推薦定義一個(gè)“SystemArchitecture”切面來(lái)捕捉通用的切入點(diǎn)表達(dá)式。一個(gè)典型的通用切面看起來(lái)可能像下面這樣:
package com.xyz.someapp; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SystemArchitecture { @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} @Pointcut("execution(* com.xyz.someapp.service.*.*(..))") public void businessService() {} @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
示例中的切入點(diǎn)定義了一個(gè)你可以在任何需要切入點(diǎn)表達(dá)式的地方可引用的切面。比如,為了使service層事務(wù)化,你可以寫(xiě)成:
<aop:config> <aop:advisor pointcut="com.xyz.someapp.SystemArchitecture.businessService()" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
我們將在第?6.3?節(jié) “基于Schema的AOP支持”中討論 <aop:config>
和<aop:advisor>
標(biāo)簽。在第?9?章 事務(wù)管理
中討論事務(wù)標(biāo)簽。
Spring AOP 用戶(hù)可能會(huì)經(jīng)常使用 execution
切入點(diǎn)指示符。執(zhí)行表達(dá)式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回類(lèi)型模式(上面代碼片斷中的ret-type-pattern),名字模式和參數(shù)模式以外,
所有的部分都是可選的。返回類(lèi)型模式?jīng)Q定了方法的返回類(lèi)型必須依次匹配一個(gè)連接點(diǎn)。
你會(huì)使用的最頻繁的返回類(lèi)型模式是*
,它代表了匹配任意的返回類(lèi)型。
一個(gè)全限定的類(lèi)型名將只會(huì)匹配返回給定類(lèi)型的方法。名字模式匹配的是方法名。
你可以使用*
通配符作為所有或者部分命名模式。
參數(shù)模式稍微有點(diǎn)復(fù)雜:()
匹配了一個(gè)不接受任何參數(shù)的方法,
而(..)
匹配了一個(gè)接受任意數(shù)量參數(shù)的方法(零或者更多)。
模式(*)
匹配了一個(gè)接受一個(gè)任何類(lèi)型的參數(shù)的方法。
模式(*,String)
匹配了一個(gè)接受兩個(gè)參數(shù)的方法,第一個(gè)可以是任意類(lèi)型,
第二個(gè)則必須是String類(lèi)型。更多的信息請(qǐng)參閱AspectJ編程指南中
語(yǔ)言語(yǔ)義的部分。
下面給出一些通用切入點(diǎn)表達(dá)式的例子。
任意公共方法的執(zhí)行:
execution(public * *(..))
任何一個(gè)名字以“set”開(kāi)始的方法的執(zhí)行:
execution(* set*(..))
AccountService
接口定義的任意方法的執(zhí)行:
execution(* com.xyz.service.AccountService.*(..))
在service包中定義的任意方法的執(zhí)行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定義的任意方法的執(zhí)行:
execution(* com.xyz.service..*.*(..))
在service包中的任意連接點(diǎn)(在Spring AOP中只是方法執(zhí)行):
within(com.xyz.service.*)
在service包或其子包中的任意連接點(diǎn)(在Spring AOP中只是方法執(zhí)行):
within(com.xyz.service..*)
實(shí)現(xiàn)了AccountService
接口的代理對(duì)象的任意連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行):
this(com.xyz.service.AccountService)
'this'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得代理對(duì)象在通知體內(nèi)可用。
實(shí)現(xiàn)AccountService
接口的目標(biāo)對(duì)象的任意連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行):
target(com.xyz.service.AccountService)
'target'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得目標(biāo)對(duì)象在通知體內(nèi)可用。
任何一個(gè)只接受一個(gè)參數(shù),并且運(yùn)行時(shí)所傳入的參數(shù)是Serializable
接口的連接點(diǎn)(在Spring AOP中只是方法執(zhí)行)
args(java.io.Serializable)
'args'在綁定表單中更加常用:- 請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得方法參數(shù)在通知體內(nèi)可用。
請(qǐng)注意在例子中給出的切入點(diǎn)不同于 execution(* *(java.io.Serializable))
:
args版本只有在動(dòng)態(tài)運(yùn)行時(shí)候傳入?yún)?shù)是Serializable時(shí)才匹配,而execution版本在方法簽名中聲明只有一個(gè)
Serializable
類(lèi)型的參數(shù)時(shí)候匹配。
目標(biāo)對(duì)象中有一個(gè) @Transactional
注解的任意連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行)
@target(org.springframework.transaction.annotation.Transactional)
'@target'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得注解對(duì)象在通知體內(nèi)可用。
任何一個(gè)目標(biāo)對(duì)象聲明的類(lèi)型有一個(gè) @Transactional
注解的連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行):
@within(org.springframework.transaction.annotation.Transactional)
'@within'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得注解對(duì)象在通知體內(nèi)可用。
任何一個(gè)執(zhí)行的方法有一個(gè) @Transactional
注解的連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行)
@annotation(org.springframework.transaction.annotation.Transactional)
'@annotation'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得注解對(duì)象在通知體內(nèi)可用。
任何一個(gè)只接受一個(gè)參數(shù),并且運(yùn)行時(shí)所傳入的參數(shù)類(lèi)型具有@Classified
注解的連接點(diǎn)(在Spring AOP中只是方法執(zhí)行)
@args(com.xyz.security.Classified)
'@args'在綁定表單中更加常用:-
請(qǐng)參見(jiàn)后面的通知一節(jié)中了解如何使得注解對(duì)象在通知體內(nèi)可用。
任何一個(gè)在名為'tradeService
'的Spring bean之上的連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行):
bean(tradeService)
任何一個(gè)在名字匹配通配符表達(dá)式'*Service
'的Spring bean之上的連接點(diǎn)
(在Spring AOP中只是方法執(zhí)行):
bean(*Service)
通知是跟一個(gè)切入點(diǎn)表達(dá)式關(guān)聯(lián)起來(lái)的,并且在切入點(diǎn)匹配的方法執(zhí)行之前或者之后或者前后運(yùn)行。 切入點(diǎn)表達(dá)式可能是指向已命名的切入點(diǎn)的簡(jiǎn)單引用或者是一個(gè)已經(jīng)聲明過(guò)的切入點(diǎn)表達(dá)式。
一個(gè)切面里使用 @Before
注解聲明前置通知:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
如果使用一個(gè)in-place 的切入點(diǎn)表達(dá)式,我們可以把上面的例子換個(gè)寫(xiě)法:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
返回后通知通常在一個(gè)匹配的方法返回的時(shí)候執(zhí)行。使用 @AfterReturning
注解來(lái)聲明:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
說(shuō)明:你可以在相同的切面里定義多個(gè)通知,或者其他成員。 我們只是在展示如何定義一個(gè)簡(jiǎn)單的通知。這些例子主要的側(cè)重點(diǎn)是正在討論的問(wèn)題。
有時(shí)候你需要在通知體內(nèi)得到返回的值。你可以使用@AfterReturning
接口的形式來(lái)綁定返回值:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
在 returning
屬性中使用的名字必須對(duì)應(yīng)于通知方法內(nèi)的一個(gè)參數(shù)名。
當(dāng)一個(gè)方法執(zhí)行返回后,返回值作為相應(yīng)的參數(shù)值傳入通知方法。
一個(gè)returning
子句也限制了只能匹配到返回指定類(lèi)型值的方法。
(在本例子中,返回值是Object
類(lèi),也就是說(shuō)返回任意類(lèi)型都會(huì)匹配)
請(qǐng)注意當(dāng)使用后置通知時(shí)不允許返回一個(gè)完全不同的引用。
拋出異常通知在一個(gè)方法拋出異常后執(zhí)行。使用@AfterThrowing
注解來(lái)聲明:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
你通常會(huì)想要限制通知只在某種特殊的異常被拋出的時(shí)候匹配,你還希望可以在通知體內(nèi)得到被拋出的異常。
使用throwing
屬性不僅可以限制匹配的異常類(lèi)型(如果你不想限制,請(qǐng)使用
Throwable
作為異常類(lèi)型),還可以將拋出的異常綁定到通知的一個(gè)參數(shù)上。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
在throwing
屬性中使用的名字必須與通知方法內(nèi)的一個(gè)參數(shù)對(duì)應(yīng)。
當(dāng)一個(gè)方法因拋出一個(gè)異常而中止后,這個(gè)異常將會(huì)作為那個(gè)對(duì)應(yīng)的參數(shù)送至通知方法。
throwing
子句也限制了只能匹配到拋出指定異常類(lèi)型的方法
(上面的示例為DataAccessException
)。
不論一個(gè)方法是如何結(jié)束的,最終通知都會(huì)運(yùn)行。使用@After
注解來(lái)聲明。最終通知必須準(zhǔn)備處理正常返回和異常返回兩種情況。通常用它來(lái)釋放資源。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
最后一種通知是環(huán)繞通知。環(huán)繞通知在一個(gè)方法執(zhí)行之前和之后執(zhí)行。它使得通知有機(jī)會(huì) 在一個(gè)方法執(zhí)行之前和執(zhí)行之后運(yùn)行。而且它可以決定這個(gè)方法在什么時(shí)候執(zhí)行,如何執(zhí)行,甚至是否執(zhí)行。 環(huán)繞通知經(jīng)常在某線程安全的環(huán)境下,你需要在一個(gè)方法執(zhí)行之前和之后共享某種狀態(tài)的時(shí)候使用。 請(qǐng)盡量使用最簡(jiǎn)單的滿足你需求的通知。(比如如果簡(jiǎn)單的前置通知也可以適用的情況下不要使用環(huán)繞通知)。
環(huán)繞通知使用@Around
注解來(lái)聲明。通知的第一個(gè)參數(shù)必須是
ProceedingJoinPoint
類(lèi)型。在通知體內(nèi),調(diào)用
ProceedingJoinPoint
的proceed()
方法會(huì)導(dǎo)致
后臺(tái)的連接點(diǎn)方法執(zhí)行。proceed
方法也可能會(huì)被調(diào)用并且傳入一個(gè)
Object[]
對(duì)象-該數(shù)組中的值將被作為方法執(zhí)行時(shí)的參數(shù)。
當(dāng)傳入一個(gè)Object[]
對(duì)象的時(shí)候,處理的方法與通過(guò)AspectJ編譯器處理環(huán)繞通知略有不同。
對(duì)于使用傳統(tǒng)AspectJ語(yǔ)言寫(xiě)的環(huán)繞通知來(lái)說(shuō),傳入?yún)?shù)的數(shù)量必須和傳遞給環(huán)繞通知的參數(shù)數(shù)量匹配
(不是后臺(tái)的連接點(diǎn)接受的參數(shù)數(shù)量),并且特定順序的傳入?yún)?shù)代替了將要綁定給連接點(diǎn)的原始值
(如果你看不懂不用擔(dān)心)。Spring采用的方法更加簡(jiǎn)單并且能更好匹配它基于代理(proxy-based)的執(zhí)行語(yǔ)法,
如果你使用AspectJ的編譯器和編織器來(lái)編譯為Spring而寫(xiě)的@AspectJ切面和處理參數(shù),你只需要知道這一區(qū)別即可。
有一種方法可以讓你寫(xiě)出100%兼容Spring AOP和AspectJ的表達(dá)式,我們將會(huì)在后續(xù)的通知參數(shù)的章節(jié)中討論它。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect public class AroundExample { @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; } }
方法的調(diào)用者得到的返回值就是環(huán)繞通知返回的值。 例如:一個(gè)簡(jiǎn)單的緩存切面,如果緩存中有值,就返回該值,否則調(diào)用proceed()方法。 請(qǐng)注意proceed可能在通知體內(nèi)部被調(diào)用一次,許多次,或者根本不被調(diào)用,所有這些都是合法的。
Spring 2.0 提供了完整的通知類(lèi)型 - 這意味著你可以在通知簽名中聲明所需的參數(shù), (就像我們?cè)谇懊婵吹降暮笾煤彤惓Mㄖ粯樱┒豢偸鞘褂肙bject[]。 我們將會(huì)看到如何使得參數(shù)和其他上下文值對(duì)通知體可用。 首先讓我們看以下如何編寫(xiě)普通的通知以找出正在被通知的方法。
任何通知方法可以將第一個(gè)參數(shù)定義為org.aspectj.lang.JoinPoint
類(lèi)型
(環(huán)繞通知需要定義第一個(gè)參數(shù)為ProceedingJoinPoint
類(lèi)型,
它是 JoinPoint
的一個(gè)子類(lèi))。JoinPoint
接口提供了一系列有用的方法,比如 getArgs()
(返回方法參數(shù))、
getThis()
(返回代理對(duì)象)、getTarget()
(返回目標(biāo))、
getSignature()
(返回正在被通知的方法相關(guān)信息)和 toString()
(打印出正在被通知的方法的有用信息)。詳細(xì)的內(nèi)容請(qǐng)參考JavaDoc。
我們已經(jīng)看到了如何綁定返回值或者異常(使用后置通知和異常通知)。為了可以在通知體內(nèi)訪問(wèn)參數(shù),
你可以使用args
來(lái)綁定。如果在一個(gè)args表達(dá)式中應(yīng)該使用類(lèi)型名字的地方
使用一個(gè)參數(shù)名字,那么當(dāng)通知執(zhí)行的時(shí)候?qū)?yīng)的參數(shù)值將會(huì)被傳遞進(jìn)來(lái)。用一個(gè)例子應(yīng)該會(huì)使它變得清晰。
假使你想要通知以一個(gè)Account對(duì)象作為第一個(gè)參數(shù)的DAO操作的執(zhí)行,
你想要在通知體內(nèi)也能訪問(wèn)account對(duì)象,可以編寫(xiě)如下的代碼:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
public void validateAccount(Account account) {
// ...
}
切入點(diǎn)表達(dá)式的 args(account,..)
部分有兩個(gè)目的:首先它保證了
只會(huì)匹配那些接受至少一個(gè)參數(shù)的方法的執(zhí)行,而且傳入的參數(shù)必須是Account
類(lèi)型的實(shí)例,
其次它使得在通知體內(nèi)可以通過(guò)account
參數(shù)訪問(wèn)實(shí)際的Account
對(duì)象。
另外一個(gè)辦法是定義一個(gè)切入點(diǎn),這個(gè)切入點(diǎn)在匹配某個(gè)連接點(diǎn)的時(shí)候“提供”了
Account
對(duì)象的值,然后直接從通知中訪問(wèn)那個(gè)命名切入點(diǎn)??雌饋?lái)和下面的示例一樣:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
有興趣的讀者請(qǐng)參閱 AspectJ 編程指南了解更詳細(xì)的內(nèi)容。
代理對(duì)象(this
)、目標(biāo)對(duì)象(target
)
和注解(@within, @target, @annotation, @args
)都可以用一種類(lèi)似的格式來(lái)綁定。
以下的例子展示了如何使用 @Auditable
注解來(lái)匹配方法執(zhí)行,并提取Audit代碼。
首先是@Auditable
注解的定義:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); }
然后是匹配@Auditable
方法執(zhí)行的通知:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && " +
"@annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
綁定在通知上的參數(shù)依賴(lài)切入點(diǎn)表達(dá)式的匹配名,并借此在(通知和切入點(diǎn))的方法簽名中聲明參數(shù)名。 參數(shù)名無(wú)法 通過(guò)Java反射來(lái)獲取,所以Spring AOP使用如下的策略來(lái)確定參數(shù)名字:
如果參數(shù)名字已經(jīng)被用戶(hù)明確指定,則使用指定的參數(shù)名: 通知和切入點(diǎn)注解有一個(gè)額外的"argNames"屬性,該屬性用來(lái)指定所注解的方法的參數(shù)名 - 這些參數(shù)名在運(yùn)行時(shí)是可以 訪問(wèn)的。例子如下:
@Before(
value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
如果第一個(gè)參數(shù)是JoinPoint
,
ProceedingJoinPoint
,
或者JoinPoint.StaticPart
類(lèi)型,
你可以在“argNames”屬性的值中省去參數(shù)的名字。例如,如果你修改前面的通知來(lái)獲取連接點(diǎn)對(duì)象,
"argNames"屬性就不必包含它:
@Before(
value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
對(duì)于第一個(gè)JoinPoint
,
ProceedingJoinPoint
,和
JoinPoint.StaticPart
類(lèi)型的參數(shù)特殊處理特別適合
沒(méi)有集合其它連接上下文的通知。在這種情部下,你可以簡(jiǎn)單的省略“argNames”屬性。
例如,下面的通知不需要聲明“argNames”屬性:
@Before(
"com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
使用'argNames'
屬性有一點(diǎn)笨拙,所以如果'argNames'
屬性沒(méi)有被指定,Spring AOP將查看類(lèi)的debug信息并嘗試從本地的變量表確定參數(shù)名。只要類(lèi)編譯時(shí)有debug信息,
(最少要有'-g:vars'
)這個(gè)信息將會(huì)出現(xiàn)。打開(kāi)這個(gè)標(biāo)志編譯的結(jié)果是:
(1)你的代碼稍微容易理解(反向工程),
(2)class文件的大小稍微有些大(通常不重要),
(3)你的編譯器將不會(huì)應(yīng)用優(yōu)化去移除未使用的本地變量。換句話說(shuō),打開(kāi)這個(gè)標(biāo)志創(chuàng)建時(shí)你應(yīng)當(dāng)不會(huì)遇到困難。
如果一個(gè)@AspectJ切面已經(jīng)被AspectJ編譯器(ajc)編譯過(guò),即使沒(méi)有debug信息,
也不需要添加argNames
參數(shù),因?yàn)榫幾g器會(huì)保留必需的信息。
如果不加上必要的debug信息來(lái)編譯的話,Spring AOP將會(huì)嘗試推斷綁定變量到參數(shù)的配對(duì)。
(例如,要是只有一個(gè)變量被綁定到切入點(diǎn)表達(dá)式,通知方法只接受一個(gè)參數(shù), 配對(duì)是顯而易見(jiàn)的)。
如果變量的綁定不明確,將會(huì)拋出一個(gè)AmbiguousBindingException
異常。
如果以上所有策略都失敗了,將會(huì)拋出一個(gè)IllegalArgumentException
異常。
我們之前提過(guò)我們將會(huì)討論如何編寫(xiě)一個(gè)帶參數(shù)的的proceed()調(diào)用, 使得在Spring AOP和AspectJ中都能正常工作。解決方法是僅僅確保通知簽名按順序綁定方法參數(shù)。例如:
@Around("execution(List<Account> find*(..)) &&" + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
大多數(shù)情況下你都會(huì)這樣綁定(就像上面的例子那樣)。
如果有多個(gè)通知想要在同一連接點(diǎn)運(yùn)行會(huì)發(fā)生什么?Spring AOP遵循跟AspectJ一樣的優(yōu)先規(guī)則來(lái)確定通知執(zhí)行的順序。 在“進(jìn)入”連接點(diǎn)的情況下,最高優(yōu)先級(jí)的通知會(huì)先執(zhí)行(所以給定的兩個(gè)前置通知中,優(yōu)先級(jí)高的那個(gè)會(huì)先執(zhí)行)。 在“退出”連接點(diǎn)的情況下,最高優(yōu)先級(jí)的通知會(huì)最后執(zhí)行。(所以給定的兩個(gè)后置通知中, 優(yōu)先級(jí)高的那個(gè)會(huì)第二個(gè)執(zhí)行)。
當(dāng)定義在不同的切面里的兩個(gè)通知都需要在一個(gè)相同的連接點(diǎn)中運(yùn)行,
那么除非你指定,否則執(zhí)行的順序是未知的。你可以通過(guò)指定優(yōu)先級(jí)來(lái)控制執(zhí)行順序。
在標(biāo)準(zhǔn)的Spring方法中可以在切面類(lèi)中實(shí)現(xiàn)org.springframework.core.Ordered
接口或者用Order
注解做到這一點(diǎn)。在兩個(gè)切面中,
Ordered.getValue()
方法返回值(或者注解值)較低的那個(gè)有更高的優(yōu)先級(jí)。
當(dāng)定義在相同的切面里的兩個(gè)通知都需要在一個(gè)相同的連接點(diǎn)中運(yùn)行, 執(zhí)行的順序是未知的(因?yàn)檫@里沒(méi)有方法通過(guò)反射javac編譯的類(lèi)來(lái)獲取聲明順序)。 考慮在每個(gè)切面類(lèi)中按連接點(diǎn)壓縮這些通知方法到一個(gè)通知方法,或者重構(gòu)通知的片段到各自的切面類(lèi)中 - 它能在切面級(jí)別進(jìn)行排序。
引入(在AspectJ中被稱(chēng)為inter-type聲明)使得一個(gè)切面可以定義被通知對(duì)象實(shí)現(xiàn)給定的接口, 并且可以為那些對(duì)象提供具體的實(shí)現(xiàn)。
使用@DeclareParents
注解來(lái)定義引入。這個(gè)注解用來(lái)定義匹配的類(lèi)型
擁有一個(gè)新的父類(lèi)(所以有了這個(gè)名字)。比如,給定一個(gè)接口UsageTracked
,
和接口的具體實(shí)現(xiàn)DefaultUsageTracked
類(lèi),
接下來(lái)的切面聲明了所有的service接口的實(shí)現(xiàn)都實(shí)現(xiàn)了UsageTracked
接口。
(比如為了通過(guò)JMX輸出統(tǒng)計(jì)信息)。
@Aspect public class UsageTracking { @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.SystemArchitecture.businessService() &&" + "this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } }
實(shí)現(xiàn)的接口通過(guò)被注解的字段類(lèi)型來(lái)決定。@DeclareParents
注解的
value
屬性是一個(gè)AspectJ的類(lèi)型模式:- 任何匹配類(lèi)型的bean都會(huì)實(shí)現(xiàn)
UsageTracked
接口。請(qǐng)注意,在上面的前置通知的例子中,service beans
可以直接用作UsageTracked
接口的實(shí)現(xiàn)。如果需要編程式的來(lái)訪問(wèn)一個(gè)bean,
你可以這樣寫(xiě):
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
(這是一個(gè)高級(jí)主題,所以如果你剛開(kāi)始學(xué)習(xí)AOP你可以跳過(guò)它到后面的章節(jié))
默認(rèn)情況下,在application context中每一個(gè)切面都會(huì)有一個(gè)實(shí)例。AspectJ把這個(gè)叫做單例化模型。
也可以用其他的生命周期來(lái)定義切面:Spring支持AspectJ的 perthis
和pertarget
實(shí)例化模型(現(xiàn)在還不支持percflow、percflowbelow
和pertypewithin
)。
一個(gè)"perthis" 切面通過(guò)在@Aspect
注解中指定perthis
子句來(lái)聲明。讓我們先來(lái)看一個(gè)例子,然后解釋它是如何運(yùn)作的:
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}
這個(gè)'perthis'
子句的效果是每個(gè)獨(dú)立的service對(duì)象執(zhí)行一個(gè)業(yè)務(wù)時(shí)都會(huì)
創(chuàng)建一個(gè)切面實(shí)例(切入點(diǎn)表達(dá)式所匹配的連接點(diǎn)上的每一個(gè)獨(dú)立的對(duì)象都會(huì)綁定到'this'上)。
在service對(duì)象上第一次調(diào)用方法的時(shí)候,切面實(shí)例將被創(chuàng)建。切面在service對(duì)象失效的同時(shí)失效。
在切面實(shí)例被創(chuàng)建前,所有的通知都不會(huì)被執(zhí)行,一旦切面對(duì)象創(chuàng)建完成,
定義的通知將會(huì)在匹配的連接點(diǎn)上執(zhí)行,但是只有當(dāng)service對(duì)象是和切面關(guān)聯(lián)的才可以。
請(qǐng)參閱 AspectJ 編程指南了解更多關(guān)于per-clauses的信息。
'pertarget'
實(shí)例模型的跟“perthis”完全一樣,只不過(guò)是為每個(gè)匹配于連接點(diǎn)
的獨(dú)立目標(biāo)對(duì)象創(chuàng)建一個(gè)切面實(shí)例。
現(xiàn)在你已經(jīng)看到了每個(gè)獨(dú)立的部分是如何運(yùn)作的了,是時(shí)候把他們放到一起做一些有用的事情了!
因?yàn)椴l(fā)的問(wèn)題,有時(shí)候業(yè)務(wù)服務(wù)(business services)可能會(huì)失?。ɡ?,死鎖失?。H绻匦聡L試一下,
很有可能就會(huì)成功。對(duì)于業(yè)務(wù)服務(wù)來(lái)說(shuō),重試幾次是很正常的(Idempotent操作不需要用戶(hù)參與,否則會(huì)得出矛盾的結(jié)論)
我們可能需要透明的重試操作以避免客戶(hù)看到一個(gè)PessimisticLockingFailureException
異常。
很明顯,在一個(gè)橫切多層的情況下,這是非常有必要的,因此通過(guò)切面來(lái)實(shí)現(xiàn)是很理想的。
因?yàn)槲覀兿胍卦嚥僮鳎覀儠?huì)需要使用到環(huán)繞通知,這樣我們就可以多次調(diào)用proceed()方法。 下面是簡(jiǎn)單的切面實(shí)現(xiàn):
@Aspect 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; } @Around("com.xyz.myapp.SystemArchitecture.businessService()") 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ā)生。
請(qǐng)注意這個(gè)時(shí)候我們所有的businessService()
方法都會(huì)使用這個(gè)重試策略。
我們首先會(huì)嘗試處理,如果得到一個(gè)PessimisticLockingFailureException
異常,
我們僅僅重試直到耗盡所有預(yù)設(shè)的重試次數(shù)。
對(duì)應(yīng)的Spring配置如下:
<aop:aspectj-autoproxy/> <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
為改進(jìn)切面,使之僅僅重試idempotent操作,我們可以定義一個(gè)
Idempotent
注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
并且對(duì)service操作的實(shí)現(xiàn)進(jìn)行注解。為了只重試idempotent操作,切面的修改只需要改寫(xiě)切入點(diǎn)表達(dá)式,
使得只匹配@Idempotent
操作:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable { ... }