javascript
使用Spring进行面向切面编程(AOP)---讲解+代码
6.1. 簡(jiǎn)介
6.2.4.1. 前置通知(Before advice)
一個(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() { // ... }
}
6.2.4.2. 返回后通知(After returning advice)返回后通知通常在一個(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è)通知,或者其他成員。我們只是在展示如何定義一個(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 子句也限制了只能匹配到返回指定類型值的方法。 (在本例子中,返回值是 Object 類,也就是說(shuō)返回任意類型都會(huì)匹配)
6.2.4.3. 拋出后通知(After throwing advice)拋出后通知在一個(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 屬性不光可以限制匹配的異常類型(如果你不想限制,請(qǐng)使用 Throwable 作為異常類型),還可以將拋出的異常綁定到通知的一個(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 子句也限制了只能匹配到拋出指定異常類型的方法(上面的示例為 DataAccessException)。
6.2.4.4. 后通知(After (finally) advice)不論一個(gè)方法是如何結(jié)束的,在它結(jié)束后(finally)后通知(After (finally) advice)都會(huì)運(yùn)行。 使用 @After 注解來(lái)聲明。這個(gè)通知必須做好處理正常返回和異常返回兩種情況。通常用來(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() { // ... }
}
6.2.4.5. 環(huán)繞通知(Around Advice)最后一種通知是環(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)單的滿足你需求的通知。(比如如果前置通知(before advice)也可以適用的情況下不要使用環(huán)繞通知)。環(huán)繞通知使用 @Around 注解來(lái)聲明。通知的第一個(gè)參數(shù)必須是 ProceedingJoinPoint 類型。 在通知體內(nèi),調(diào)用 ProceedingJoinPoint 的 proceed() 方法將會(huì)導(dǎo)致潛在的連接點(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的,我們將會(huì)在后續(xù)的通知參數(shù)(advice parameters)的章節(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)用。
6.2.4.6. 通知參數(shù)(Advice parameters)Spring 2.0 提供了完整的通知類型 - 這意味著你可以在通知簽名中聲明所需的參數(shù),(就像在以前的例子中我們看到的返回值和拋出異常一樣)而不總是使用Object[]。 我們將會(huì)看到如何在通知體內(nèi)訪問(wèn)參數(shù)和其他上下文相關(guān)的值。首先讓我們看以下如何編寫(xiě)普通的通知以找出正在被通知的方法。
6.2.4.6.1. 訪問(wèn)當(dāng)前的連接點(diǎn)任何通知方法可以將第一個(gè)參數(shù)定義為 org.aspectj.lang.JoinPoint 類型 (環(huán)繞通知需要定義為 ProceedingJoinPoint 類型的, 它是 JoinPoint 的一個(gè)子類。) JoinPoint 接口提供了一系列有用的方法, 比如 getArgs()(返回方法參數(shù))、getThis()(返回代理對(duì)象)、getTarget()(返回目標(biāo))、getSignature()(返回正在被通知的方法相關(guān)信息)和 toString()(打印出正在被通知的方法的有用信息)。
6.2.4.6.2. 傳遞參數(shù)給通知(Advice)我們已經(jīng)看到了如何綁定返回值或者異常(使用后置通知(after returning)和異常后通知(after throwing advice)。 為了可以在通知(adivce)體內(nèi)訪問(wèn)參數(shù),你可以使用 args 來(lái)綁定。 如果在一個(gè)參數(shù)表達(dá)式中應(yīng)該使用類型名字的地方使用一個(gè)參數(shù)名字,那么當(dāng)通知執(zhí)行的時(shí)候?qū)?yīng)的參數(shù)值將會(huì)被傳遞進(jìn)來(lái)。 可能給出一個(gè)例子會(huì)更好理解。假使你想要通知(advise)接受某個(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 類型的實(shí)例, 其次它使得可以在通知體內(nèi)通過(guò) account 參數(shù)來(lái)訪問(wèn)那個(gè)account參數(shù)。另外一個(gè)辦法是定義一個(gè)切入點(diǎn),這個(gè)切入點(diǎn)在匹配某個(gè)連接點(diǎn)的時(shí)候“提供”了一個(gè)Account對(duì)象, 然后直接從通知中訪問(wèn)那個(gè)命名的切入點(diǎn)。你可以這樣寫(xiě):
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" + "args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) { // ..}如果想要知道更詳細(xì)的內(nèi)容,請(qǐng)參閱 AspectJ 編程指南。代理對(duì)象(this)、目標(biāo)對(duì)象(target) 和注解(@within, @target, @annotation, @args)都可以用一種簡(jiǎn)單格式綁定。 以下的例子展示了如何使用 @Auditable 注解來(lái)匹配方法執(zhí)行,并提取AuditCode。首先是 @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(); // ...}
6.2.4.6.3. 決定參數(shù)名綁定在通知上的參數(shù)依賴切入點(diǎn)表達(dá)式的匹配名,并借此在(通知(advice)和切入點(diǎn)(pointcut))的方法簽名中聲明參數(shù)名。 參數(shù)名 無(wú)法 通過(guò)Java反射來(lái)獲取,所以Spring AOP使用如下的策略來(lái)決定參數(shù)名字:如果參數(shù)名字已經(jīng)被用戶明確指定,則使用指定的參數(shù)名: 通知(advice)和切入點(diǎn)(pointcut)注解有一個(gè)額外的"argNames"屬性,該屬性用來(lái)指定所注解的方法的參數(shù)名 - 這些參數(shù)名在運(yùn)行時(shí)是 可以 訪問(wèn)的。例子如下:
@Before( value="com.xyz.lib.Pointcuts.anyPublicMethod() && " + "@annotation(auditable)", argNames="auditable")
public void audit(Auditable auditable) {
????????? AuditCode code = auditable.value(); // ...
}
如果一個(gè)@AspectJ切面已經(jīng)被AspectJ編譯器(ajc)編譯過(guò)了,那么就不需要再添加 argNames 參數(shù)了,因?yàn)榫幾g器會(huì)自動(dòng)完成這一工作。使用 'argNames' 屬性有點(diǎn)不那么優(yōu)雅,所以如果沒(méi)有指定'argNames' 屬性, Spring AOP 會(huì)尋找類的debug信息,并且嘗試從本地變量表(local variable table)中來(lái)決定參數(shù)名字。 只要編譯的時(shí)候使用了debug信息(至少要使用 '-g:vars' ),就可獲得這些信息。
使用這個(gè)flag編譯的結(jié)果是:
(1)你的代碼將能夠更加容易的讀懂(反向工程),
(2)生成的class文件會(huì)稍許大一些(不重要的),
(3)移除不被使用的本地變量的優(yōu)化功能將會(huì)失效。 換句話說(shuō),你在使用這個(gè)flag的時(shí)候不會(huì)遇到任何困難。如果不加上debug信息來(lái)編譯的話,Spring AOP將會(huì)嘗試推斷參數(shù)的綁定。 (例如,要是只有一個(gè)變量被綁定到切入點(diǎn)表達(dá)式(pointcut expression)、通知方法(advice method)將會(huì)接受這個(gè)參數(shù), 這是顯而易見(jiàn)的)。 如果變量的綁定不明確,將會(huì)拋出一個(gè) AmbiguousBindingException 異常。如果以上所有策略都失敗了,將會(huì)拋出一個(gè) IllegalArgumentException 異常。
6.3.3. 聲明通知和@AspectJ風(fēng)格一樣,基于schema的風(fēng)格也支持5種通知類型并且兩者具有同樣的語(yǔ)義。
6.3.3.1. 通知(Advice)Before通知在匹配方法執(zhí)行前進(jìn)入。在里面使用元素進(jìn)行聲明。 ...這里 dataAccessOperation 是一個(gè)頂級(jí)()切入點(diǎn)的id。 要定義內(nèi)置切入點(diǎn),可將 pointcut-ref 屬性替換為 pointcut 屬性: ...我們已經(jīng)在@AspectJ風(fēng)格章節(jié)中討論過(guò)了,使用命名切入點(diǎn)能夠明顯的提高代碼的可讀性。Method屬性標(biāo)識(shí)了提供了通知的主體的方法(doAccessCheck)。這個(gè)方法必須定義在包含通知的切面元素所引用的bean中。 在一個(gè)數(shù)據(jù)訪問(wèn)操作執(zhí)行之前(執(zhí)行連接點(diǎn)和切入點(diǎn)表達(dá)式匹配),切面中的"doAccessCheck"會(huì)被調(diào)用。
6.3.3.2. 返回后通知(After returning advice)After returning通知在匹配的方法完全執(zhí)行后運(yùn)行。和Before通知一樣,可以在里面聲明。例如: ...和@AspectJ風(fēng)格一樣,通知主體可以接收返回值。使用returning屬性來(lái)指定接收返回值的參數(shù)名: ...doAccessCheck方法必須聲明一個(gè)名字叫 retVal 的參數(shù)。 參數(shù)的類型強(qiáng)制匹配,和先前我們?cè)?#64;AfterReturning中講到的一樣。例如,方法簽名可以這樣聲明:public void doAccessCheck(Object retVal) {...
6.3.3.3. 拋出異常后通知(After throwing advice)After throwing通知在匹配方法拋出異常退出時(shí)執(zhí)行。在 中使用after-throwing元素來(lái)聲明: ...和@AspectJ風(fēng)格一樣,可以從通知體中獲取拋出的異常。 使用throwing屬性來(lái)指定異常的名稱,用這個(gè)名稱來(lái)獲取異常: ...doRecoveryActions方法必須聲明一個(gè)名字為 dataAccessEx 的參數(shù)。 參數(shù)的類型強(qiáng)制匹配,和先前我們?cè)?#64;AfterThrowing中講到的一樣。例如:方法簽名可以如下這般聲明:public void doRecoveryActions(DataAccessException dataAccessEx) {...
6.3.3.4. 后通知(After (finally) advice)After (finally)通知在匹配方法退出后執(zhí)行。使用 after 元素來(lái)聲明: ...
6.3.3.5. 通知Around通知是最后一種通知類型。Around通知在匹配方法運(yùn)行期的“周圍”執(zhí)行。 它有機(jī)會(huì)在目標(biāo)方法的前面和后面執(zhí)行,并決定什么時(shí)候運(yùn)行,怎么運(yùn)行,甚至是否運(yùn)行。 Around通知經(jīng)常在需要在一個(gè)方法執(zhí)行前或后共享狀態(tài)信息,并且是線程安全的情況下使用(啟動(dòng)和停止一個(gè)計(jì)時(shí)器就是一個(gè)例子)。 注意選擇能滿足你需求的最簡(jiǎn)單的通知類型(i.e.如果簡(jiǎn)單的before通知就能做的事情絕對(duì)不要使用around通知)。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) Section 6.2.4.5, “環(huán)繞通知(Around Advice)” 中提到的一些注意點(diǎn)。 ...doBasicProfiling 通知的實(shí)現(xiàn)和@AspectJ中的例子完全一樣(當(dāng)然要去掉注解):public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal;}6.3.3.6. 通知參數(shù)Schema-based聲明風(fēng)格和@AspectJ支持一樣,支持通知的全名形式 - 通過(guò)通知方法參數(shù)名字來(lái)匹配切入點(diǎn)參數(shù)。 參見(jiàn) Section 6.2.4.6, “通知參數(shù)(Advice parameters)” 獲取詳細(xì)信息。如果你希望顯式指定通知方法的參數(shù)名(而不是依靠先前提及的偵測(cè)策略),可以通過(guò) arg-names 屬性來(lái)實(shí)現(xiàn)。示例如下:The arg-names attribute accepts a comma-delimited list of parameter names.arg-names屬性接受由逗號(hào)分割的參數(shù)名列表。
總結(jié)
以上是生活随笔為你收集整理的使用Spring进行面向切面编程(AOP)---讲解+代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 买二手车考虑哪些因素
- 下一篇: 金寨今天有上电瓶车牌照的吗?