1.aop理論知識javascript
橫切性關注點:對哪些方法攔截,攔截後怎麼處理,這些關注就稱之爲橫切性關注點.java
Aspect(切面):指橫切性關注點的抽象即爲切面,它與類類似,只是二者的關注點不同,類是對物體特徵的抽象,而切面是橫切性關注點的抽象。
Joinpoint(鏈接點):所謂鏈接點是指那些被攔截到的點。在Spring中,這些點指的是方法,由於Spring只支持方法類型的鏈接點,實際上joinpoint還能夠是field或類構造器。
Pointcut(切入點):所謂切入點是指咱們要對那些joinpoin進行攔截的定義。
Advice(通知):所謂通知是指攔截到joinpoint以後所要作的事情就是通知。通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知。
Target(目標對象):代理的目標對象
Weave(織入):指將aspects應用到target對象並致使proxy對象建立的過程稱爲織入。
Introduction(引入):在不修改類代碼的前提下,Introduction能夠在運行期爲類動態地添加一些方法或Field.spring
要進行AOP編程,首先咱們要在Spring的配置文件中引入aop命名空間,以下面文件中的紅色部分所示,配置文件的內容以下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd ">
<aop:aspectj-autoproxy /><!-- 啓動對@Aspectj註解的支持-->
</beans>express
Spring提供了兩種切面使用方式,實際工做中咱們能夠選用其中任何一種:編程
1.使用XML配置方式進行AOP開發app
2. 基於註解方式進行AOP開發測試
首先,必須在配置文件中beans的節點下有以下配置信息:<aop:aspectj-autoproxy/> -- 啓動對@Aspectj註解的支持spa
下面經過實例來說解基於spring的AOP實現:.net
1.導入spring開發的基本包(包括切面及註解包)代理
2.定義PersonService接口及bean類PersonServiceBean,以下所示:
Java代碼
Java代碼
3.編寫切面類MyInterceptor,代碼以下:
Java代碼
說明:
1. //* cn.itcast.service..*.*(..))第一個*表明返回的類型能夠是任意的,service後面的..表明包service以及下面的全部子包,*表明這些包下面的全部類;.*表明全部這些類下面的全部方法;(..)表明這些方法的輸入參數能夠是0或多個.
2.在前置通知的方法中有一個參數,而後再把此參數做爲攔截條件(便是說攔截帶有一個String類型參數的方法)。args的名字和beforeAdd 方法參數名字相同。
3. afterReturningRes方法的參數就是要返回的參數類型,returning標記的就是的結果,它的取值與該方法參數名相同。
4. 環繞通知要注意的是方法的參數及拋出異常類型的固定寫法(方法名能夠是任意得),另在該方法中必須執行pjp.proceed()才能讓環繞通知中的兩處打印代碼得以執行。便是說要想環繞通知的攔截處理代碼起做用必須調用pjp.proceed方法。 補充:環繞通知一般能夠用來測試方法的執行時間,在pjp.proceed前獲取一個時間,在pjp.proceed方法後再獲取一個時間。最後兩個時間相減便可得方法執行時間。
上面基於註解的AOP換成基於XML的方式以下所示:
Java代碼
當基於xml方式時,遇到一個未解決問題:不能成功傳參給前置通知。當我定義一個前置通知以下所示時:
Java代碼
運行測試程序,他不能成功攔截方法:
Java代碼
上面的這個攔截類中的攔截方法除了切入點條件外,還必須知足一些輔助條件,使用攔截更精確了,若是你不想太精確,則能夠簡單點以下所示:
Java代碼
替換成xml方式爲:
Java代碼
當不能前置通知傳參數時,基於xml方式配置的這個AOP也能成功攔截save()方法.
Java代碼
4.將這些內歸入Spring管理(要想切面類起做用,首先要把切面類歸入spring容器管理。),配置文件以下所示:
Xml代碼
5. 編寫junit測試單元以下:
Java代碼
1. 執行測試程序後,控制檯打印以下所示:
前置通知:xx
進入方法
我是save()方法
後置通知:0
最終通知
退出方法
從打印的結果能夠看出當執行
Java代碼
方法時,它有String類型的輸入參數,攔截時前置通知執行了,因爲它的返回類型爲int因此後置通知也執行.
2.執行方法:
Java代碼
控制檯打印的信息以下所示:
進入方法
我是getPersonName()方法
最終通知
退出方法
因爲getPersonName()方法是Integer類型的輸入參數,同時它的返回類型又是String類型的,因此前置通知和後置通知都沒有執行.
3. 將PersonServiceBean中程序
Java代碼
前的註釋去掉,從新執行
Java代碼
控制檯打印信息以下所示:
前置通知:xx
進入方法
例外通知:java.lang.ArithmeticException: / by zero
最終通知
當獲取代理對象並調用save方法時會拋出異常,例外通知便會得以執行。
4.因爲開啓了切面編程功能,因此當咱們獲取一個被切面類監控管理的bean對象—PersonServiceBean時,它實際上獲取的是此對象的一個代理對象,而在spring中對代理對象的處理有以下原則:(1)若是要代理的對象實現了接口,則會按照Proxy的方式來產生代理對象,這便是說產生的代理對象只能是接口類型,好比起用上面注掉的代碼
Java代碼
就會報錯,報錯信息以下所示:java.lang.ClassCastException: $Proxy12 cannot be cast to cn.itcast.service.impl.PersonServiceBean.
(2)要代理的對象未實現接口,則按cglib方式來產生代理對象。讓PersonServiceBean再也不實現PersonService接口。這時只能執行
Java代碼
(3)另外還要注意:要想spring的切面技術起做用,被管理的bean對象只能是經過spring容器獲取的對象。好比這裏若是直接new出PersonServiceBean對象,則new出的對象是不能被spring的切面類監控管理。若是執行測試:
Java代碼
則不會攔截save()方法,save()方法正常執行,控制檯打印信息以下所示:我是save()方法.
小結:(1)聲明aspect的切面類要歸入spring容器管理才能起做用。(2)被管理的bean實例要經過容器的getBeans方法獲取。 (3)依據被管理的bean是否實現接口,spring採起兩種方式來產生代理對象。(4)在xml文件中啓用<aop:aspectj-autoproxy/>
有疑問的地方:
若是我在MyInterceptor類中調換後置通知我最終通知的定義順序,以下所示:
Java代碼
則控制檯打倒信息以下所示:
前置通知:xx
進入方法
我是save()方法
最終通知
後置通知:0
退出方法
則最終通知與後置通知的執行順序與MyInterceptor類中通知定義的順序相一致.但若是你把最終通知的定義放在MyInterceptor類的最前面,它的執行順序也與上面的一致.
同時若是將最終通知的定義位於例外通知的前面,則再執行測試的第3步,也會出現與測試3不一致的結果,打印結果以下所示:
前置通知:xx
進入方法
最終通知
例外通知:java.lang.ArithmeticException: / by zero
執行順序也與它們的定義順序一致.
無論通知的定義順序如何,反正前置通知會第一個執行,最終通知與後置通知和例外通知定義的順序不一致時,他們執行的順序也會發生改變,最終通知竟能夠先於例外通知和後置通知先執行.由於在Spring裏有可能在同一個AOP代理裏混合通知器和通知類型。 例如,你能夠在一個代理配置裏使用一個攔截環繞通知,一個異常通知和一個前置通知:Spring將負責自動建立所需的攔截器鏈。 可能Spring建立攔截器鏈時與通知定義的順序有必定關係,因此當最終通知的定義先於後置通知和例外通知時,它也將先於他倆執行.只是個人一種猜想.官方的解釋以下:
下面這段話是Spring reference中的原話,不過我也沒太看明白是咋回事.
通知順序
若是有多個通知想要在同一鏈接點運行會發生什麼?Spring AOP遵循跟AspectJ同樣的優先規則來肯定通知執行的順序。 在「進入」鏈接點的狀況下,最高優先級的通知會先執行(因此給定的兩個前置通知中,優先級高的那個會先執行)。 在「退出」鏈接點的狀況下,最高優先級的通知會最後執行。(因此給定的兩個後置通知中, 優先級高的那個會第二個執行)。
當定義在不一樣的切面裏的兩個通知都須要在一個相同的鏈接點中運行, 那麼除非你指定,不然執行的順序是未知的。你能夠經過指定優先級來控制執行順序。 在標準的Spring方法中能夠在切面類中實現org.springframework.core.Ordered 接口或者用Order註解作到這一點。在兩個切面中, Ordered.getValue()方法返回值(或者註解值)較低的那個有更高的優先級。
當定義在相同的切面裏的兩個通知都須要在一個相同的鏈接點中運行, 執行的順序是未知的(由於這裏沒有方法經過反射javac編譯的類來獲取聲明順序)。 考慮在每一個切面類中按鏈接點壓縮這些通知方法到一個通知方法,或者重構通知的片斷到各自的切面類中 - 它能在切面級別進行排序。
5.解析切入點表達式
下面給出一些通用切入點表達式的例子。
任意公共方法的執行:
execution(public * *(..))
任何一個名字以「set」開始的方法的執行:
execution(* set*(..))
AccountService
接口定義的任意方法的執行:
execution(* com.xyz.service.AccountService.*(..))
在service包中定義的任意方法的執行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定義的任意方法的執行:
execution(* com.xyz.service..*.*(..))
execution(java.lang.String com.xyz.service..*.*(..))
execution(* com.xyz.service..*.*(java.lang.String,..))
execution(!void com.xyz.service..*.*(..))