天天看點

Spring中的AOP(三)——基于Annotation的配置方式(一)

    aspectj允許使用注解用于定義切面、切入點和增強處理,而spring架構則可以識别并根據這些注解來生成aop代理。spring隻是使用了和aspectj 5一樣的注解,但并沒有使用aspectj的編譯器或者織入器,底層依然使用springaop來實作,依然是在運作時動态生成aop代理,是以不需要增加額外的編譯,也不需要aspectj的織入器支援。而aspectj采用編譯時增強,是以aspectj需要使用自己的編譯器來編譯java檔案,還需要織入器。

    為了啟用spring對@aspectj切面配置的支援,并保證spring容器中的目标bean被一個或多個切面自動增強,必須在spring配置檔案中配置如下内容(第4、9、10、15行):

<a href="http://my.oschina.net/itblog/blog/210718#">?</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>&lt;?</code><code>xml</code> <code>version</code><code>=</code><code>"1.0"</code> <code>encoding</code><code>=</code><code>"utf-8"</code><code>?&gt;</code>

<code>&lt;</code><code>beans</code> <code>xmlns</code><code>=</code><code>"http://www.springframework.org/schema/beans"</code>

<code>    </code><code>xmlns:xsi</code><code>=</code><code>"http://www.w3.org/2001/xmlschema-instance"</code>

<code>    </code><code>xmlns:aop</code><code>=</code><code>"http://www.springframework.org/schema/aop"</code>

<code>    </code><code>xmlns:context</code><code>=</code><code>"http://www.springframework.org/schema/context"</code>

<code>    </code><code>xsi:schemalocation="</code>

<code>        </code><code>http://www.springframework.org/schema/beans </code>

<code>        </code><code>http://www.springframework.org/schema/beans/spring-beans-3.0.xsd</code>

<code>        </code><code>http://www.springframework.org/schema/aop </code>

<code>        </code><code>http://www.springframework.org/schema/aop/spring-aop-3.0.xsd</code>

<code>        </code><code>http://www.springframework.org/schema/context</code>

<code>            </code><code>http://www.springframework.org/schema/context/spring-context-3.0.xsd"&gt;</code>

<code>            </code> 

<code>    </code><code>&lt;!-- 啟動@aspectj支援 --&gt;</code>

<code>    </code><code>&lt;</code><code>aop:aspectj-autoproxy</code><code>/&gt;</code>

<code>&lt;/</code><code>beans</code><code>&gt;</code>

    所謂自動增強,指的是spring會判斷一個或多個切面是否需要對指定的bean進行增強,并據此自動生成相應的代理,進而使得增強處理在合适的時候被調用。如果不打算使用xml schema的配置方式,則應該在spring配置檔案中增加如下片段來啟用@aspectj支援(即上面的&lt;aop:aspectj-autoproxy /&gt;和下面建立bean的方式選擇一種即可啟用@aspectj支援):

<code>&lt;</code><code>bean</code> <code>class</code><code>=</code><code>"org.springframework.aop.aspectj.annotation.annotationawareaspectjautoproxycreator"</code> <code>/&gt;</code>

    上面配置的是一個bean後處理器,該處理器将會為容器中bean生成aop代理。

    為了在spring應用中啟動@aspectj支援,還需要在用用的類加載路徑下增加兩個aspectj庫:aspectweaver.jar和aspectjrt.jar,直接使用aspectj安裝路徑下的lib目錄下的這兩個jar檔案即可,當然,也可以在spring解壓縮檔案夾的lib/aspectj路徑下找到它們。下面是項目内容的截圖:

Spring中的AOP(三)——基于Annotation的配置方式(一)

定義切面bean

    當啟用了@aspectj支援後,隻要我們在spring容器中配置一個帶@aspectj注釋的bean,spring将會自動識别該bean,并将該bean作為切面處理。下面是一個例子:

<code>@aspect</code>

<code>public</code> <code>class</code> <code>logaspect {</code>

<code>}</code>

    切面類(用@aspect修飾的類)和其他類一樣可以有方法和屬性的定義,還可能包括切入點、增強處理的定義。當我們使用@aspect來修飾一個java類後,spring将不會把該bean當成元件bean處理,是以當spring容器檢測到某個bean使用了@aspectj标注之後,負責自動增強的後處理bean将會忽略該bean,不會對該bean進行任何增強處理。

使用before增強處理

    當我們在一個切面類裡使用@before來标注一個方法時,該方法将作為before增強處理。使用@before标注時,通常需要指定一個value屬性值,該屬性值指定一個切入點表達式(既可以是一個已有的切入點,也可以直接定義切入點表達式),用于指定該增強處理将被織入哪些切入點。看例子:

<code>package</code> <code>com.abc.advice;</code>

<code>import</code> <code>org.aspectj.lang.annotation.aspect;</code>

<code>import</code> <code>org.aspectj.lang.annotation.before;</code>

<code>public</code> <code>class</code> <code>beforeadvicetest {</code>

<code>    </code><code>//比對com.abc.service下的類中以before開始的方法</code>

<code>    </code><code>@before</code><code>(</code><code>"execution(* com.abc.service.*.before*(..))"</code><code>)</code>

<code>    </code><code>public</code> <code>void</code> <code>permissioncheck() {</code>

<code>        </code><code>system.out.println(</code><code>"模拟權限檢查"</code><code>);</code>

<code>    </code><code>}</code>

    上面的程式使用@aspect修飾了beforeadvicetest類,這表明該類是一個切面類,在該貼面裡定義了一個permissioncheck方法——這個方法本來沒有什麼特殊之處,但因為使用了@before來标注該方法,這就将該方法轉換成一個before增強處理。這個@before注解中,直接指定了切入點表達式,指定com.abc.service包下的類中以before開始的方法的執行作為切入點。現假設我們在com.abc.service下有一個這樣一個類:

<code>package</code> <code>com.abc.service;</code>

<code>import</code> <code>org.springframework.stereotype.component;</code>

<code>@component</code>

<code>public</code> <code>class</code> <code>advicemanager {</code>

<code>    </code><code>//這個方法将被beforeadvicetest類的permissioncheck比對到</code>

<code>    </code><code>public</code> <code>void</code> <code>beforeadvice() {</code>

<code>        </code><code>system.out.println(</code><code>"方法: beforeadvicetest"</code><code>);</code>

    從上面的代碼來看,這個advicemanager是一個純淨的java類,它絲毫不知道将被誰來增強,也不知道将被進行怎樣的增強——正式因為advicemanager類的這種“無知”,才是aop的最大魅力:目标類可以被無限的增強。

    在spring配置檔案中配置自動搜尋bean元件,配置自動搜尋切面類,springaop自動對bean元件進行增強,下面是spring配置檔案代碼:

17

18

19

20

21

22

<code>        </code><code>http://www.springframework.org/schema/context/spring-context-3.0.xsd"&gt;</code>

<code>    </code> 

<code>    </code><code>&lt;!-- 指定自動搜尋bean元件,自動搜尋切面類 --&gt;</code>

<code>    </code><code>&lt;</code><code>context:component-scan</code> <code>base-package</code><code>=</code><code>"com.abc.service,com.abc.advice"</code><code>&gt;</code>

<code>        </code><code>&lt;</code><code>context:include-filter</code> <code>type</code><code>=</code><code>"annotation"</code> 

<code>            </code><code>expression</code><code>=</code><code>"org.aspectj.lang.annotation.aspect"</code> <code>/&gt;</code>

<code>    </code><code>&lt;/</code><code>context:component-scan</code><code>&gt;</code>

    主程式非常簡單,通過spring容器擷取advicemanager bean,并調用bean的beforeadvice方法:

<code>package</code> <code>com.abc.main;</code>

<code>import</code> <code>org.springframework.context.applicationcontext;</code>

<code>import</code> <code>org.springframework.context.support.classpathxmlapplicationcontext;</code>

<code>import</code> <code>com.abc.service.advicemanager;</code>

<code>@suppresswarnings</code><code>(</code><code>"resource"</code><code>)</code>

<code>public</code> <code>class</code> <code>aoptest {</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args) {</code>

<code>        </code><code>applicationcontext context = </code>

<code>            </code><code>new</code> <code>classpathxmlapplicationcontext(</code><code>"applicationcontext.xml"</code><code>);</code>

<code>        </code><code>advicemanager manager = context.getbean(advicemanager.</code><code>class</code><code>);</code>

<code>        </code><code>manager.beforeadvice();</code>

    執行主程式,将看到以下結果:

Spring中的AOP(三)——基于Annotation的配置方式(一)

    使用before增強處理隻能在目标方法執行之前織入增強,使用before增強處理無需理會目标方法的執行,是以before處理無法阻止目标方法的執行。before增強處理執行時,目标方法還未獲得執行機會,是以before增強處理無法通路目标方法的傳回值。

使用afterreturning增強處理

    和使用@before注解的使用類似,使用@afterreturning來标注一個afterreturning增強處理,該處理将在目标方法正常完成後被織入。使用@afterreturning時可以指定兩個屬性:

pointcut/value:這兩個屬性的作用是一樣的,都用于指定該切入點對應的切入表達式。同樣的,既可以是一個已有的切入點,也可以是直接定義的切入點。當指定了pointcut屬性後,value的屬性值将會被覆寫

returning:指定一個傳回值形參名,增強處理定義的方法可以通過該形參名來通路目标方法的傳回值。

    在com.abc.advice包下面增加afterreturningadvicetest,這個類定義了一個afterreturning增強處理:

<code>import</code> <code>org.aspectj.lang.annotation.afterreturning;</code>

<code>public</code> <code>class</code> <code>afterreturningadvicetest {</code>

<code>    </code><code>//比對com.abc.service下的類中以afterreturning開始的方法</code>

<code>    </code><code>@afterreturning</code><code>(returning=</code><code>"returnvalue"</code><code>, </code>

<code>        </code><code>pointcut=</code><code>"execution(* com.abc.service.*.afterreturning(..))"</code><code>)</code>

<code>    </code><code>public</code> <code>void</code> <code>log(object returnvalue){</code>

<code>        </code><code>system.out.println(</code><code>"目标方法傳回值:"</code> <code>+ returnvalue);</code>

<code>        </code><code>system.out.println(</code><code>"模拟日志記錄功能..."</code><code>);</code>

    并在advicemanager類中增加以下内容:

<code>//将被afterreturningadvicetest的log方法比對</code>

<code>public</code> <code>string afterreturning() {</code>

<code>    </code><code>system.out.println(</code><code>"方法:afterreturning"</code><code>);</code>

<code>    </code><code>return</code> <code>"afterreturning方法"</code><code>;</code>

    正如上面程式中看到的,程式中使用@afterreturning注解時,指定了一個returning屬性,該屬性的傳回值是returnvalue,這表明允許在增強方法log中使用名為returnvalue的形參,該形參代表目标方法的傳回值。在測試類aoptest的main方法中增加調用本方法的語句,運作測試類,可以看到以下結果:

Spring中的AOP(三)——基于Annotation的配置方式(一)

    @afterreturning注解的returning屬性所指定的形參名必須對應增強進行中的一個形參名,當目标方法執行以後,傳回值作為相應的參數傳入給增強處理方法。

    需要注意的是,使用@afterreturning屬性還有一個額外的作用,它可用于限定切入點之比對具有對應傳回值類型的方法——假設上面的log方法的參數returnvalue的類型為string,那麼該切入點隻比對com.abc.service.impl包下的傳回值為string的所有方法。當然,上面的log方法傳回值類型為object,表明該切入點可比對任何傳回值的方法。除此之外,雖然afterreturning增強處理可以通路到目标方法的傳回值,但它不可改變這個傳回值。

使用afterthrowing增強處理

    使用@afterthrowing注解可用于标注一個afterthrowing增強處理,這個處理主要用于處理陳旭中未處理的異常。使用這個注解時可以指定兩個屬性:

throwing:指定一個傳回值形參名,增強處理定義的方法可通過該形參名來通路目标方法中所抛出的異常對象。

     在com.abc.advice包下面增加afterthrowingadvicetest,這個類定義了一個afterthrowing增強處理:

<code>import</code> <code>org.aspectj.lang.annotation.afterthrowing;</code>

<code>public</code> <code>class</code> <code>afterthrowingadvicetest {</code>

<code>    </code><code>@afterthrowing</code><code>(throwing=</code><code>"ex"</code><code>,</code>

<code>        </code><code>pointcut=</code><code>"execution(* com.abc.service.*.afterthrow*(..))"</code><code>)</code>

<code>    </code><code>public</code> <code>void</code> <code>handleexception(throwable ex) {</code>

<code>        </code><code>system.out.println(</code><code>"目标方法抛出異常:"</code> <code>+ex);</code>

<code>        </code><code>system.out.println(</code><code>"模拟異常處理"</code><code>);</code>

    并在advicemanager類中增加以下内容:

<code>//将被afterthrowingadvicetest的handleexception方法比對</code>

<code>public</code> <code>void</code> <code>afterthrowing() {</code>

<code>    </code><code>system.out.println(</code><code>"方法: afterthrowing"</code><code>);</code>

<code>    </code><code>try</code> <code>{</code>

<code>        </code><code>int</code> <code>a = </code><code>10</code> <code>/ </code><code>0</code><code>;</code>

<code>    </code><code>} </code><code>catch</code> <code>(arithmeticexception ae) {</code>

<code>        </code><code>system.out.println(</code><code>"算術異常已被處理"</code><code>);</code>

<code>    </code><code>string s = </code><code>null</code><code>;</code>

<code>    </code><code>system.out.println(s.substring(</code><code>0</code><code>,</code><code>3</code><code>));</code>

    正如上面程式中看到的,程式中使用@afterthrowing注解時,指定了一個throwing屬性,該屬性的值是ex,這表明允許在增強方法log中使用名為ex的形參,該形參代表目标方法的抛出的異常對象。運作測試類,可以看到以下結果:

Spring中的AOP(三)——基于Annotation的配置方式(一)

    需要注意的是:如果一個異常在程式内部已經處理,那麼spring aop将不會處理該異常。隻有當目标方法抛出一個未處理的異常時,該異常将會作為對應的形參傳給增強處理的方法。和afterreturning類似的是,正确方法的參數類型可以限定切點隻比對指定類型的異常——假如上面的handleexception方法的參數類型為nullpointerexception,那麼如果目标方法隻抛出了arithmaticexception,則spring aop将不會處理這個異常。當然,handleexception的參數類型為throwable,則比對了所有的exception。

    從測試結果中可以看到,afterthrowing處理雖然可以對目标方法的異常進行處理,但這種處理與直接使用catch捕捉不同:catch捕捉意味着完全處理該異常,如果catch塊中沒有重新抛出新異常,則該方法可以正常結束;而afterthrowing處理雖然處理了該異常,但它不能完全處理該異常,這個異常依然會傳播到上一級調用者(本例中為jvm,故會導緻程式終止)。