天天看點

使用Spring進行面向切面程式設計(AOP)---講解+代碼

6.1. 簡介

6.2.4.1. 前置通知(before advice)

一個切面裡使用 @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() { // ... }

}

如果使用一個in-place 的切入點表達式,我們可以把上面的例子換個寫法:

        @before("execution(* com.xyz.myapp.dao.*.*(..))")

        public void doaccesscheck() { // ... }

6.2.4.2. 傳回後通知(after returning advice)傳回後通知通常在一個比對的方法傳回的時候執行。使用 @afterreturning 注解來聲明:

import org.aspectj.lang.annotation.afterreturning;

public class afterreturningexample {

          @afterreturning("com.xyz.myapp.systemarchitecture.dataaccessoperation()")

          public void doaccesscheck() { // ... }

說明:你可以在同一個切面裡定義多個通知,或者其他成員。我們隻是在展示如何定義一個簡單的通知。這些例子主要的側重點是正在讨論的問題。有時候你需要在通知體内得到傳回的值。你可以使用以 @afterreturning 接口的形式來綁定傳回值:

          @afterreturning( pointcut="com.xyz.myapp.systemarchitecture.dataaccessoperation()", returning="retval")

          public void doaccesscheck(object retval) { // ... }

在 returning 屬性中使用的名字必須對應于通知方法内的一個參數名。 當一個方法執行傳回後,傳回值作為相應的參數值傳入通知方法。 一個 returning 子句也限制了隻能比對到傳回指定類型值的方法。 (在本例子中,傳回值是 object 類,也就是說傳回任意類型都會比對)

6.2.4.3. 抛出後通知(after throwing advice)抛出後通知在一個方法抛出異常後執行。使用 @afterthrowing 注解來聲明:

import org.aspectj.lang.annotation.afterthrowing;

public class afterthrowingexample {

          @afterthrowing("com.xyz.myapp.systemarchitecture.dataaccessoperation()")

          public void dorecoveryactions() { // ... }

你通常會想要限制通知隻在某種特殊的異常被抛出的時候比對,你還希望可以在通知體内得到被抛出的異常。 使用 throwing 屬性不光可以限制比對的異常類型(如果你不想限制,請使用 throwable 作為異常類型),還可以将抛出的異常綁定到通知的一個參數上。

          @afterthrowing( pointcut="com.xyz.myapp.systemarchitecture.dataaccessoperation()", throwing="ex")

          public void dorecoveryactions(dataaccessexception ex) { // ... }

在 throwing 屬性中使用的名字必須與通知方法内的一個參數對應。 當一個方法因抛出一個異常而中止後,這個異常将會作為那個對應的參數送至通知方法。 throwing 子句也限制了隻能比對到抛出指定異常類型的方法(上面的示例為 dataaccessexception)。

6.2.4.4. 後通知(after (finally) advice)不論一個方法是如何結束的,在它結束後(finally)後通知(after (finally) advice)都會運作。 使用 @after 注解來聲明。這個通知必須做好處理正常傳回和異常傳回兩種情況。通常用來釋放資源。

import org.aspectj.lang.annotation.after;

public class afterfinallyexample {

          @after("com.xyz.myapp.systemarchitecture.dataaccessoperation()")

          public void doreleaselock() { // ... }

6.2.4.5. 環繞通知(around advice)最後一種通知是環繞通知。環繞通知在一個方法執行之前和之後執行。 它使得通知有機會既在一個方法執行之前又在執行之後運作。并且,它可以決定這個方法在什麼時候執行,如何執行,甚至是否執行。 環繞通知經常在在某線程安全的環境下,你需要在一個方法執行之前和之後共享某種狀态的時候使用。 請盡量使用最簡單的滿足你需求的通知。(比如如果前置通知(before advice)也可以适用的情況下不要使用環繞通知)。環繞通知使用 @around 注解來聲明。通知的第一個參數必須是

proceedingjoinpoint 類型。 在通知體内,調用 proceedingjoinpoint 的 proceed() 方法将會導緻潛在的連接配接點方法執行。 proceed 方法也可能會被調用并且傳入一個 object[] 對象-該數組将作為方法執行時候的參數。當傳入一個 object[] 對象的時候,處理的方法與通過aspectj編譯器處理環繞通知略有不同。 對于使用傳統aspectj語言寫的環繞通知來說,傳入參數的數量必須和傳遞給環繞通知的參數數量比對(不是背景的連接配接點接受的參數數量),并且特定順序的傳入參數代替了将要綁定給連接配接點的原始值(如果你看不懂不用擔心)。

spring采用的方法更加簡單并且更好得和他的基于代理(proxy-based),隻比對執行的文法相适用。 如果你适用aspectj的編譯器和編織器來編譯為spring而寫的@aspectj切面和處理參數,你隻需要了解這一差別即可。 有一種方法可以讓你寫出100%相容spring aop和aspectj的,我們将會在後續的通知參數(advice parameters)的章節中讨論它。

import org.aspectj.lang.annotation.around;

import org.aspectj.lang.proceedingjoinpoint;

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;

           }

方法的調用者得到的傳回值就是環繞通知傳回的值。 例如:一個簡單的緩存切面,如果緩存中有值,就傳回該值,否則調用proceed()方法。 請注意proceed可能在通知體内部被調用一次,許多次,或者根本不被調用。

6.2.4.6. 通知參數(advice parameters)spring 2.0 提供了完整的通知類型 - 這意味着你可以在通知簽名中聲明所需的參數,(就像在以前的例子中我們看到的傳回值和抛出異常一樣)而不總是使用object[]。 我們将會看到如何在通知體内通路參數和其他上下文相關的值。首先讓我們看以下如何編寫普通的通知以找出正在被通知的方法。

6.2.4.6.1. 通路目前的連接配接點任何通知方法可以将第一個參數定義為 org.aspectj.lang.joinpoint 類型 (環繞通知需要定義為 proceedingjoinpoint 類型的, 它是 joinpoint 的一個子類。) joinpoint 接口提供了一系列有用的方法, 比如 getargs()(傳回方法參數)、getthis()(傳回代理對象)、gettarget()(傳回目标)、getsignature()(傳回正在被通知的方法相關資訊)和 tostring()(列印出正在被通知的方法的有用資訊)。

6.2.4.6.2. 傳遞參數給通知(advice)我們已經看到了如何綁定傳回值或者異常(使用後置通知(after returning)和異常後通知(after throwing advice)。 為了可以在通知(adivce)體内通路參數,你可以使用 args 來綁定。 如果在一個參數表達式中應該使用類型名字的地方使用一個參數名字,那麼當通知執行的時候對應的參數值将會被傳遞進來。 可能給出一個例子會更好了解。假使你想要通知(advise)接受某個account對象作為第一個參數的dao操作的執行,你想要在通知體内也能通路到account對象,你可以寫如下的代碼:

@before("com.xyz.myapp.systemarchitecture.dataaccessoperation() &&" + "args(account,..)")

public void validateaccount(account account) { // ...}

切入點表達式的 args(account,..) 部分有兩個目的: 首先它保證了隻會比對那些接受至少一個參數的方法的執行,而且傳入的參數必須是 account 類型的執行個體, 其次它使得可以在通知體内通過 account 參數來通路那個account參數。另外一個辦法是定義一個切入點,這個切入點在比對某個連接配接點的時候“提供”了一個account對象, 然後直接從通知中通路那個命名的切入點。你可以這樣寫:

@pointcut("com.xyz.myapp.systemarchitecture.dataaccessoperation() &&" + "args(account,..)")

private void accountdataaccessoperation(account account) {}

@before("accountdataaccessoperation(account)")

public void validateaccount(account account) { // ..}如果想要知道更詳細的内容,請參閱 aspectj 程式設計指南。代理對象(this)、目标對象(target) 和注解(@within, @target, @annotation, @args)都可以用一種簡單格式綁定。 以下的例子展示了如何使用 @auditable 注解來比對方法執行,并提取auditcode。首先是 @auditable 注解的定義:

@retention(retentionpolicy.runtime)

@target(elementtype.method)

public @interface auditable { auditcode value();}

然後是比對 @auditable 方法執行的通知:

@before("com.xyz.lib.pointcuts.anypublicmethod() && " + "@annotation(auditable)")

public void audit(auditable auditable) { auditcode code = auditable.value(); // ...}

6.2.4.6.3. 決定參數名綁定在通知上的參數依賴切入點表達式的比對名,并借此在(通知(advice)和切入點(pointcut))的方法簽名中聲明參數名。 參數名 無法 通過java反射來擷取,是以spring aop使用如下的政策來決定參數名字:如果參數名字已經被使用者明确指定,則使用指定的參數名: 通知(advice)和切入點(pointcut)注解有一個額外的"argnames"屬性,該屬性用來指定所注解的方法的參數名 - 這些參數名在運作時是 可以 通路的。例子如下:

@before( value="com.xyz.lib.pointcuts.anypublicmethod() && " + "@annotation(auditable)", argnames="auditable")

public void audit(auditable auditable) {

          auditcode code = auditable.value(); // ...

如果一個@aspectj切面已經被aspectj編譯器(ajc)編譯過了,那麼就不需要再添加 argnames 參數了,因為編譯器會自動完成這一工作。使用 'argnames' 屬性有點不那麼優雅,是以如果沒有指定'argnames' 屬性, spring aop 會尋找類的debug資訊,并且嘗試從本地變量表(local variable table)中來決定參數名字。 隻要編譯的時候使用了debug資訊(至少要使用 '-g:vars' ),就可獲得這些資訊。

使用這個flag編譯的結果是:

(1)你的代碼将能夠更加容易的讀懂(反向工程),

(2)生成的class檔案會稍許大一些(不重要的),

(3)移除不被使用的本地變量的優化功能将會失效。 換句話說,你在使用這個flag的時候不會遇到任何困難。如果不加上debug資訊來編譯的話,spring aop将會嘗試推斷參數的綁定。 (例如,要是隻有一個變量被綁定到切入點表達式(pointcut expression)、通知方法(advice method)将會接受這個參數, 這是顯而易見的)。 如果變量的綁定不明确,将會抛出一個 ambiguousbindingexception 異常。如果以上所有政策都失敗了,将會抛出一個 illegalargumentexception

異常。

6.3.3. 聲明通知和@aspectj風格一樣,基于schema的風格也支援5種通知類型并且兩者具有同樣的語義。

6.3.3.1. 通知(advice)before通知在比對方法執行前進入。在裡面使用元素進行聲明。 ...這裡 dataaccessoperation 是一個頂級()切入點的id。 要定義内置切入點,可将 pointcut-ref 屬性替換為 pointcut 屬性: ...我們已經在@aspectj風格章節中讨論過了,使用命名切入點能夠明顯的提高代碼的可讀性。method屬性辨別了提供了通知的主體的方法(doaccesscheck)。這個方法必須定義在包含通知的切面元素所引用的bean中。 在一個資料通路操作執行之前(執行連接配接點和切入點表達式比對),切面中的"doaccesscheck"會被調用。

6.3.3.2. 傳回後通知(after returning advice)after returning通知在比對的方法完全執行後運作。和before通知一樣,可以在裡面聲明。例如: ...和@aspectj風格一樣,通知主體可以接收傳回值。使用returning屬性來指定接收傳回值的參數名: ...doaccesscheck方法必須聲明一個名字叫 retval 的參數。 參數的類型強制比對,和先前我們在@afterreturning中講到的一樣。例如,方法簽名可以這樣聲明:public void doaccesscheck(object

retval) {...

6.3.3.3. 抛出異常後通知(after throwing advice)after throwing通知在比對方法抛出異常退出時執行。在 中使用after-throwing元素來聲明: ...和@aspectj風格一樣,可以從通知體中擷取抛出的異常。 使用throwing屬性來指定異常的名稱,用這個名稱來擷取異常: ...dorecoveryactions方法必須聲明一個名字為 dataaccessex 的參數。 參數的類型強制比對,和先前我們在@afterthrowing中講到的一樣。例如:方法簽名可以如下這般聲明:public

void dorecoveryactions(dataaccessexception dataaccessex) {...

6.3.3.4. 後通知(after (finally) advice)after (finally)通知在比對方法退出後執行。使用 after 元素來聲明: ...

6.3.3.5. 通知around通知是最後一種通知類型。around通知在比對方法運作期的“周圍”執行。 它有機會在目标方法的前面和後面執行,并決定什麼時候運作,怎麼運作,甚至是否運作。 around通知經常在需要在一個方法執行前或後共享狀态資訊,并且是線程安全的情況下使用(啟動和停止一個計時器就是一個例子)。 注意選擇能滿足你需求的最簡單的通知類型(i.e.如果簡單的before通知就能做的事情絕對不要使用around通知)。around通知使用 aop:around 元素來聲明。 通知方法的第一個參數的類型必須是

proceedingjoinpoint 類型。 在通知的主體中,調用 proceedingjoinpoint的proceed() 方法來執行真正的方法。 proceed 方法也可能會被調用并且傳入一個 object[] 對象 - 該數組将作為方法執行時候的參數。 參見 section 6.2.4.5, “環繞通知(around advice)” 中提到的一些注意點。 ...dobasicprofiling 通知的實作和@aspectj中的例子完全一樣(當然要去掉注解):public object dobasicprofiling(proceedingjoinpoint

pjp) throws throwable { // start stopwatch object retval = pjp.proceed(); // stop stopwatch return retval;}6.3.3.6. 通知參數schema-based聲明風格和@aspectj支援一樣,支援通知的全名形式 - 通過通知方法參數名字來比對切入點參數。 參見 section 6.2.4.6, “通知參數(advice parameters)” 擷取詳細資訊。如果你希望顯式指定通知方法的參數名(而不是依靠先前提及的偵測政策),可以通過

arg-names 屬性來實作。示例如下:the arg-names attribute accepts a comma-delimited list of parameter names.arg-names屬性接受由逗号分割的參數名清單。