FROM:http://www.blogjava.net/supercrsky/articles/174368.htmlhtml
面向切面編程(AOP)提供另一種角度來思考程序結構,經過這種方式彌補了面向對象編程(OOP)的不足。 除了類(classes)之外,AOP提供了 切面。切面對關注點進行模塊化,例如橫切多個類型和對象的事務管理。 (這些關注點術語一般稱做 橫切(crosscutting) 關注點。)java
Spring的一個關鍵的組件就是 AOP框架。 儘管如此,Spring IoC容器並不依賴於AOP,這意味着你能夠自由選擇是否使用AOP,AOP提供強大的中間件解決方案,這使得Spring IoC容器更加完善。程序員
Spring中所使用的AOP:express
提供聲明式企業服務,特別是爲了替代EJB聲明式服務。 最重要的服務是 聲明性事務管理(declarative transaction management) , 這個服務創建在Spring的抽象事務管理(transaction abstraction)之上。編程
容許用戶實現自定義的切面,用AOP來完善OOP的使用。api
這樣你能夠把Spring AOP看做是對Spring的一種加強,它使得Spring能夠不須要EJB就能提供聲明式事務管理;或者也可使用Spring AOP框架的所有功能來實現自定義的切面。數組
本章首先 介紹了AOP的概念,不管你打算採用哪一種風格的切面聲明,這個部分都值得你一讀。 本章剩下的部分將着重於Spring 2.0對AOP的支持; 下一章 提供了關於Spring 1.2風格的AOP概述,也許你已經在其餘書本,文章以及已有的應用程序中碰到過這種AOP風格。
若是你只打算使用通用的聲明式服務或者預先打包的聲明式中間件服務,例如緩衝池(pooling), 那麼你沒必要不直接使用Spring AOP,而本章的大部份內容也能夠直接跳過。 6.1.1. AOP概念首先讓咱們從定義一些重要的AOP概念開始。這些術語不是Spring特有的。 不幸的是,Spring術語並非特別的直觀;若是Spring使用本身的術語,將會變得更加使人困惑。
切面(Aspect): 一個關注點的模塊化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關於橫切關注點的很好的例子。 在Spring AOP中,切面可使用通用類(基於模式的風格) 或者在普通類中以 @Aspect 註解(@AspectJ風格)來實現。
鏈接點(Joinpoint): 在程序執行過程當中某個特定的點,好比某方法調用的時候或者處理異常的時候。 在Spring AOP中,一個鏈接點 老是 表明一個方法的執行。 經過聲明一個 org.aspectj.lang.JoinPoint 類型的參數可使通知(Advice)的主體部分得到鏈接點信息。
通知(Advice): 在切面的某個特定的鏈接點(Joinpoint)上執行的動做。通知有各類類型,其中包括「around」、「before」和「after」等通知。 通知的類型將在後面部分進行討論。許多AOP框架,包括Spring,都是以攔截器作通知模型,並維護一個以鏈接點爲中心的攔截器鏈。
切入點(Pointcut): 匹配鏈接點(Joinpoint)的斷言。通知和一個切入點表達式關聯,並在知足這個切入點的鏈接點上運行(例如,當執行某個特定名稱的方法時)。 切入點表達式如何和鏈接點匹配是AOP的核心:Spring缺省使用AspectJ切入點語法。
引入(Introduction): (也被稱爲內部類型聲明(inter-type declaration))。聲明額外的方法或者某個類型的字段。 Spring容許引入新的接口(以及一個對應的實現)到任何被代理的對象。 例如,你可使用一個引入來使bean實現IsModified 接口,以便簡化緩存機制。
目標對象(Target Object): 被一個或者多個切面(aspect)所通知(advise)的對象。也有人把它叫作 被通知(advised) 對象。 既然Spring AOP是經過運行時代理實現的,這個對象永遠是一個 被代理(proxied) 對象。
AOP代理(AOP Proxy): AOP框架建立的對象,用來實現切面契約(aspect contract)(包括通知方法執行等功能)。 在Spring中,AOP代理能夠是JDK動態代理或者CGLIB代理。 注意:Spring 2.0最新引入的基於模式(schema-based)風格和@AspectJ註解風格的切面聲明,對於使用這些風格的用戶來講,代理的建立是透明的。
織入(Weaving): 把切面(aspect)鏈接到其它的應用程序類型或者對象上,並建立一個被通知(advised)的對象。 這些能夠在編譯時(例如使用AspectJ編譯器),類加載時和運行時完成。 Spring和其餘純Java AOP框架同樣,在運行時完成織入。
通知的類型:
前置通知(Before advice): 在某鏈接點(join point)以前執行的通知,但這個通知不能阻止鏈接點前的執行(除非它拋出一個異常)。
返回後通知(After returning advice): 在某鏈接點(join point)正常完成後執行的通知:例如,一個方法沒有拋出任何異常,正常返回。
拋出異常後通知(After throwing advice): 在方法拋出異常退出時執行的通知。
後通知(After (finally) advice): 當某鏈接點退出的時候執行的通知(不管是正常返回仍是異常退出)。
環繞通知(Around Advice): 包圍一個鏈接點(join point)的通知,如方法調用。這是最強大的一種通知類型。 環繞通知能夠在方法調用先後完成自定義的行爲。它也會選擇是否繼續執行鏈接點或直接返回它們本身的返回值或拋出異常來結束執行。
環繞通知是最經常使用的一種通知類型。大部分基於攔截的AOP框架,例如Nanning和JBoss4,都只提供環繞通知。
跟AspectJ同樣,Spring提供全部類型的通知,咱們推薦你使用盡可能簡單的通知類型來實現須要的功能。 例如,若是你只是須要用一個方法的返回值來更新緩存,雖然使用環繞通知也能完成一樣的事情, 可是你最好使用After returning通知而不是環繞通知。 用最合適的通知類型可使得編程模型變得簡單,而且可以避免不少潛在的錯誤。 好比,你不須要調用 JoinPoint(用於Around Advice)的proceed() 方法,就不會有調用的問題。
在Spring 2.0中,全部的通知參數都是靜態類型,所以你可使用合適的類型(例如一個方法執行後的返回值類型)做爲通知的參數而不是使用一個對象數組。
切入點(pointcut)和鏈接點(join point)匹配的概念是AOP的關鍵,這使得AOP不一樣於其它僅僅提供攔截功能的舊技術。 切入點使得定位通知(advice)可獨立於OO層次。 例如,一個提供聲明式事務管理的around通知能夠被應用到一組橫跨多個對象中的方法上(例如服務層的全部業務操做)。
Spring AOP用純Java實現。它不須要專門的編譯過程。Spring AOP不須要控制類裝載器層次,所以它適用於J2EE web容器或應用服務器。
Spring目前僅支持使用方法調用做爲鏈接點(join point)(在Spring bean上通知方法的執行)。 雖然能夠在不影響到Spring AOP核心API的狀況下加入對成員變量攔截器支持,但Spring並無實現成員變量攔截器。 若是你須要把對成員變量的訪問和更新也做爲通知的鏈接點,能夠考慮其它語法的Java語言,例如AspectJ。
Spring實現AOP的方法跟其餘的框架不一樣。Spring並非要嘗試提供最完整的AOP實現(儘管Spring AOP有這個能力), 相反的,它其實側重於提供一種AOP實現和Spring IoC容器的整合,用於幫助解決在企業級開發中的常見問題。
所以,Spring AOP一般都和Spring IoC容器一塊兒使用。 Aspect使用普通的bean定義語法(儘管Spring提供了強大的「自動代理(autoproxying)」功能): 與其餘AOP實現相比這是一個顯著的區別。有些事使用Spring AOP是沒法輕鬆或者高效的完成的,好比說通知一個細粒度的對象。 這種時候,使用AspectJ是最好的選擇。不過經驗告訴咱們: 於大多數在J2EE應用中遇到的問題,只要適合AOP來解決的,Spring AOP都沒有問題,Spring AOP提供了一個很是好的解決方案。
Spring AOP歷來沒有打算經過提供一種全面的AOP解決方案來取代AspectJ。 咱們相信不管是基於代理(proxy-based )的框架好比說Spring亦或是full-blown的框架好比說是AspectJ都是頗有價值的,他們之間的關係應該是互補而不是競爭的關係。 Spring 2.0能夠無縫的整合Spring AOP,IoC 和AspectJ,使得全部的AOP應用徹底融入基於Spring的應用體系。 這樣的集成不會影響Spring AOP API或者AOP Alliance API;Spring AOP保留了向下兼容性。接下來的一章會詳細討論Spring AOP API。
Spring缺省使用J2SE 動態代理(dynamic proxies)來做爲AOP的代理。這樣任何接口均可以被代理。
Spring也支持使用CGLIB代理. 對於須要代理類而不是代理接口的時候CGLIB代理是頗有必要的。 若是一個業務對象並無實現一個接口,默認就會使用CGLIB。 此外,面向接口編程 也是一個最佳實踐,業務對象一般都會實現一個或多個接口。
此外,還能夠強制的使用CGLIB:咱們將會在之後討論這個問題,解釋問什麼你會要這麼作。
在Spring 2.0以後,Spring可能會提供多種其餘類型的AOP代理,包括了完整的生成類。這不會影響到編程模型。若是你使用Java 5的話,推薦使用Spring提供的@AspectJ切面支持,經過這種方式聲明Spring AOP中使用的切面。 "@AspectJ"使用了Java 5的註解,能夠將切面聲明爲普通的Java類。 AspectJ 5發佈的 AspectJ project 中引入了這種@AspectJ風格。 Spring 2.0 使用了和AspectJ 5同樣的註解,使用了AspectJ 提供的一個庫來作切點(pointcut)解析和匹配。 可是,AOP在運行時仍舊是純的Spring AOP,並不依賴於AspectJ 的編譯器或者織入器(weaver)。
使用AspectJ的編譯器或者織入器(weaver)的話就可使用完整的AspectJ 語言,咱們將在 Section 6.7, 「在Spring應用中使用AspectJ」 中討論這個問題。 6.2.1. 啓用@AspectJ支持爲了在Spring配置中使用@AspectJ aspects,你必須首先啓用Spring對基於@AspectJ aspects的配置支持,自動代理(autoproxying)基於通知是否來自這些切面。 自動代理是指Spring會判斷一個bean是否使用了一個或多個切面通知,並據此自動生成相應的代理以攔截其方法調用,而且確認通知是否如期進行。
經過在你的Spring的配置中引入下列元素來啓用Spring對@AspectJ的支持:
<aop:aspectj-autoproxy/>
咱們假使你正在使用 Appendix A, XML Schema-based configuration 所描述的schema支持。 關於如何在aop的命名空間中引入這些標籤,請參見 Section A.2.6, 「The aop schema」
若是你正在使用DTD,你仍舊能夠經過在你的application context中添加以下定義來啓用@AspectJ支持:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
你須要在你的應用程序的classpath中引入兩個AspectJ庫:aspectjweaver.jar 和 aspectjrt.jar。 這些庫能夠在AspectJ的安裝包(1.5.1或者以後的版本)中的 lib 目錄裏找到,或者也能夠在Spring依賴庫的 lib/aspectj 目錄下找到。
下面是在application context中的一個常見的bean定義,這個bean指向一個使用了 @Aspect 註解的bean類:
下面是 NotVeryUsefulAspect 類定義,使用了 org.aspectj.lang.annotation.Aspect 註解。
切面(用 @Aspect 註解的類)和其餘類同樣有方法和字段定義。他們也可能包括切入點,通知和引入(inter-type)聲明。
回想一下,切入點決定了鏈接點關注的內容,使得咱們能夠控制通知什麼執行。 Spring AOP只支持Spring bean方法執行鏈接點。因此你能夠把切入點看作是匹配Spring bean上的方法執行。 一個切入點聲明有兩個部分:一個包含名字和任意參數的簽名,還有一個切入點表達式,該表達式決定了咱們關注那個方法的執行。 在@AspectJ中,一個切入點實際就是一個普通的方法定義提供的一個簽名,而且切入點表達式使用 @Pointcut註解來表示。 這個方法的返回類型必須是 void。 以下的例子定義了一個切入點'transfer',這個切入點匹配了任意名爲"transfer"的方法執行:
切入點表達式,也就是 @Pointcut 註解的值,是正規的AspectJ 5切入點表達式。 若是你想要更多瞭解AspectJ的 切入點語言,請參見 AspectJ 編程指南(若是要了解基於Java 5的擴展請參閱 AspectJ 5 開發手冊) 或者其餘人寫的關於AspectJ的書,例如Colyer et. al.著的《Eclipse AspectJ》或者Ramnivas Laddad著的《AspectJ in Action》。
Spring AOP 支持在切入點表達式中使用以下的AspectJ切入點指定者:
execution - 匹配方法執行的鏈接點,這是你將會用到的Spring的最主要的切入點指定者。
within - 限定匹配特定類型的鏈接點(在使用Spring AOP的時候,在匹配的類型中定義的方法的執行)。
this - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中bean reference(Spring AOP 代理)是指定類型的實例。
target - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中目標對象(被代理的appolication object)是指定類型的實例。
args - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中參數是指定類型的實例。
@target - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中執行的對象的類已經有指定類型的註解。
@args - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中實際傳入參數的運行時類型有指定類型的註解。
@within - 限定匹配特定的鏈接點,其中鏈接點所在類型已指定註解(在使用Spring AOP的時候,所執行的方法所在類型已指定註解)。
@annotation - 限定匹配特定的鏈接點(使用Spring AOP的時候方法的執行),其中鏈接點的主題有某種給定的註解。
切入點表達式可使用using '&&', '||' 和 '!'來合併.還能夠經過名字來指向切入點表達式。 如下的例子展現了三種切入點表達式:anyPublicOperation(在一個方法執行鏈接點表明了任意public方法的執行時匹配); inTrading(在一個表明了在交易模塊中的任意的方法執行時匹配) 和 tradingOperation(在一個表明了在交易模塊中的任意的公共方法執行時匹配)。
就上所示的,從更小的命名組件來構建更加複雜的切入點表達式是一種最佳實踐。 當用名字來指定切入點時使用的是常見的Java成員可視性訪問規則。 (好比說,你能夠在同一類型中訪問私有的切入點,在繼承關係中訪問受保護的切入點,能夠在任意地方訪問公共切入點。 成員可視性訪問規則不影響到切入點的 匹配。
在AspectJ 1.5.1中有一個bug (#140357)有時候可能會致使Spring所使用的AspectJ切入點解析失敗, 即便用一個已命名的切入點來引用到另外一個同類型的切入點的時候。 在AspectJ的開發中已經解決這個bug,能夠在AspectJ的下載頁面獲得。在1.5.2發佈時將會包含這一fix。 若是你遇到了這個問題,你能夠去下載AspectJ的開發構建包,而且更新你的 aspectjweaver.jar,這是在AspectJ 1.5.2發佈前的臨時解決方案。當開發企業級應用的時候,你一般會想要從幾個切面來參考模塊化的應用和特定操做的集合。 咱們推薦定義一個「SystemArchitecture」切面來捕捉常見的切入點表達式。一個典型的切面可能看起來像下面這樣:
示例中的切入點定義一個你能夠在任何須要切入點表達式的地方可引用的切面。好比,爲了使service層事務化,你能夠寫:
在 Section 6.3, 「Schema-based AOP support」 中討論 <aop:config> 和 <aop:advisor>標籤。 在 Chapter 9, 事務管理 中討論事務標籤。
Spring AOP 用戶可能會常用 execution pointcut designator。執行表達式的格式以下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回類型模式,名字模式和參數模式之外,全部的部分都是可選的。 返回類型模式決定了方法的返回類型必須依次匹配一個鏈接點。 你會使用的最頻繁的返回類型模式是 *,它表明了匹配任意的返回類型。 一個全稱限定的類型名將只會匹配返回給定類型的方法。名字模式匹配的是方法名。 你可使用 * 通配符做爲全部或者部分命名模式。 參數模式稍微有點複雜:() 匹配了一個不接受任何參數的方法, 而 (..) 匹配了一個接受任意數量參數的方法(零或者更多)。 模式 (*) 匹配了一個接受一個任何類型的參數的方法。 模式 (*,String) 匹配了一個接受兩個參數的方法,第一個能夠是任意類型,第二個則必須是String類型。 請參見AspectJ編程指南的Language Semantics 部分。
下面給出一些常見切入點表達式的例子。
任意公共方法的執行:
execution(public * *(..))
任何一個以「set」開始的方法的執行:
execution(* set*(..))
AccountService 接口的任意方法的執行:
execution(* com.xyz.service.AccountService.*(..))
定義在service包裏的任意方法的執行:
execution(* com.xyz.service.*.*(..))
定義在service包或者子包裏的任意方法的執行:
execution(* com.xyz.service..*.*(..))
在service包裏的任意鏈接點(在Spring AOP中只是方法執行) :
within(com.xyz.service.*)
在service包或者子包裏的任意鏈接點(在Spring AOP中只是方法執行) :
within(com.xyz.service..*)
實現了 AccountService 接口的代理對象的任意鏈接點(在Spring AOP中只是方法執行) :
this(com.xyz.service.AccountService)'this'在binding form中用的更多:- 請常見如下討論通知的章節中關於如何使得代理對象能夠在通知體內訪問到的部分。
實現了 AccountService 接口的目標對象的任意鏈接點(在Spring AOP中只是方法執行) :
target(com.xyz.service.AccountService)'target'在binding form中用的更多:- 請常見如下討論通知的章節中關於如何使得目標對象能夠在通知體內訪問到的部分。
任何一個只接受一個參數,且在運行時傳入的參數實現了 Serializable 接口的鏈接點 (在Spring AOP中只是方法執行)
args(java.io.Serializable)'args'在binding form中用的更多:- 請常見如下討論通知的章節中關於如何使得方法參數能夠在通知體內訪問到的部分。
請注意在例子中給出的切入點不一樣於 execution(* *(java.io.Serializable)): args只有在動態運行時候傳入參數是可序列化的(Serializable)才匹配,而execution 在傳入參數的簽名聲明的類型實現了 Serializable 接口時候匹配。
有一個 @Transactional 註解的目標對象中的任意鏈接點(在Spring AOP中只是方法執行)
@target(org.springframework.transaction.annotation.Transactional)'@target' 也能夠在binding form中使用:請常見如下討論通知的章節中關於如何使得annotation對象能夠在通知體內訪問到的部分。
任何一個目標對象聲明的類型有一個 @Transactional 註解的鏈接點(在Spring AOP中只是方法執行)
@within(org.springframework.transaction.annotation.Transactional)'@within'也能夠在binding form中使用:- 請常見如下討論通知的章節中關於如何使得annotation對象能夠在通知體內訪問到的部分。
任何一個執行的方法有一個 @Transactional annotation的鏈接點(在Spring AOP中只是方法執行)
@annotation(org.springframework.transaction.annotation.Transactional)'@annotation' 也能夠在binding form中使用:- 請常見如下討論通知的章節中關於如何使得annotation對象能夠在通知體內訪問到的部分。
任何一個接受一個參數,而且傳入的參數在運行時的類型實現了 @Classified annotation的鏈接點(在Spring AOP中只是方法執行)
@args(com.xyz.security.Classified)'@args'也能夠在binding form中使用:- 請常見如下討論通知的章節中關於如何使得annotation對象能夠在通知體內訪問到的部分。
通知是跟一個切入點表達式關聯起來的,而且在切入點匹配的方法執行以前或者以後或者以前和以後運行。 切入點表達式多是指向已命名的切入點的簡單引用或者是一個已經聲明過的切入點表達式。
一個切面裏使用 @Before 註解聲明前置通知:
若是使用一個in-place 的切入點表達式,咱們能夠把上面的例子換個寫法:
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() { // ... } }
返回後通知一般在一個匹配的方法返回的時候執行。使用 @AfterReturning 註解來聲明:
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() { // ... } }說明:你能夠在同一個切面裏定義多個通知,或者其餘成員。咱們只是在展現如何定義一個簡單的通知。這些例子主要的側重點是正在討論的問題。
有時候你須要在通知體內獲得返回的值。你可使用以 @AfterReturning 接口的形式來綁定返回值:
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 屬性中使用的名字必須對應於通知方法內的一個參數名。 當一個方法執行返回後,返回值做爲相應的參數值傳入通知方法。 一個 returning 子句也限制了只能匹配到返回指定類型值的方法。 (在本例子中,返回值是 Object 類,也就是說返回任意類型都會匹配)
拋出後通知在一個方法拋出異常後執行。使用 @AfterThrowing 註解來聲明:
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() { // ... } }
你一般會想要限制通知只在某種特殊的異常被拋出的時候匹配,你還但願能夠在通知體內獲得被拋出的異常。 使用 throwing 屬性不光能夠限制匹配的異常類型(若是你不想限制,請使用 Throwable 做爲異常類型),還能夠將拋出的異常綁定到通知的一個參數上。
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 屬性中使用的名字必須與通知方法內的一個參數對應。 當一個方法因拋出一個異常而停止後,這個異常將會做爲那個對應的參數送至通知方法。 throwing 子句也限制了只能匹配到拋出指定異常類型的方法(上面的示例爲 DataAccessException)。
不論一個方法是如何結束的,在它結束後(finally)後通知(After (finally) advice)都會運行。 使用 @After 註解來聲明。這個通知必須作好處理正常返回和異常返回兩種狀況。一般用來釋放資源。
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() { // ... } }
最後一種通知是環繞通知。環繞通知在一個方法執行以前和以後執行。 它使得通知有機會既在一個方法執行以前又在執行以後運行。而且,它能夠決定這個方法在何時執行,如何執行,甚至是否執行。 環繞通知常常在在某線程安全的環境下,你須要在一個方法執行以前和以後共享某種狀態的時候使用。 請儘可能使用最簡單的知足你需求的通知。(好比若是前置通知(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.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; } }
方法的調用者獲得的返回值就是環繞通知返回的值。 例如:一個簡單的緩存切面,若是緩存中有值,就返回該值,不然調用proceed()方法。 請注意proceed可能在通知體內部被調用一次,許屢次,或者根本不被調用。
Spring 2.0 提供了完整的通知類型 - 這意味着你能夠在通知簽名中聲明所需的參數,(就像在之前的例子中咱們看到的返回值和拋出異常同樣)而不老是使用Object[]。 咱們將會看到如何在通知體內訪問參數和其餘上下文相關的值。首先讓咱們看如下如何編寫普通的通知以找出正在被通知的方法。
任何通知方法能夠將第一個參數定義爲 org.aspectj.lang.JoinPoint 類型 (環繞通知須要定義爲 ProceedingJoinPoint 類型的, 它是 JoinPoint 的一個子類。) JoinPoint 接口提供了一系列有用的方法, 好比 getArgs()(返回方法參數)、getThis()(返回代理對象)、getTarget()(返回目標)、getSignature()(返回正在被通知的方法相關信息)和 toString()(打印出正在被通知的方法的有用信息)。
咱們已經看到了如何綁定返回值或者異常(使用後置通知(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(); // ... }
綁定在通知上的參數依賴切入點表達式的匹配名,並藉此在(通知(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 異常。
咱們以前提過咱們將會討論如何編寫一個 帶參數的 的proceed()調用,使得不論在Spring AOP中仍是在AspectJ都能正常工做。 解決方法是保證通知簽名依次綁定方法參數。好比說:
@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}); }
大多數狀況下你都會這樣綁定(就像上面的例子那樣)。
若是有多個通知想要在同一鏈接點運行會發生什麼?Spring AOP 的執行通知的順序跟AspectJ的同樣。 在「進入」鏈接點的狀況下,最高優先級的通知會先執行(因此上面給出的兩個前置通知(before advice)中,優先級高的那個會先執行)。 在「退出」鏈接點的狀況下,最高優先級的通知會最後執行。(因此上面給出的兩個前置通知(before advice)中,優先級高的那個會第二個執行)。 對於定義在相同切面的通知,根據聲明的順序來肯定執行順序。好比下面這個切面:
@Aspect public class AspectWithMultipleAdviceDeclarations { @Pointcut("execution(* foo(..))") public void fooExecution() {} @Before("fooExecution()") public void doBeforeOne() { // .. } @Before("fooExecution()") public void doBeforeTwo() { // .. } @AfterReturning("fooExecution()") public void doAfterOne() { // .. } @AfterReturning("fooExecution()") public void doAfterTwo() { //.. } }
這樣,假使對於任何一個名字爲foo的方法的執行, doBeforeOne、doBeforeTwo、doAfterOne 和 doAfterTwo 通知方法都須要運行。 執行順序將按照聲明的順序來肯定。在這個例子中,執行的結果會是:
doBeforeOne doBeforeTwo foo doAfterOne doAfterTwo
換言之,由於doBeforeOne先定義,它會先於doBeforeTwo執行,而doAfterTwo後於doAfterOne定義,因此它會在doAfterOne以後執行。 只須要記住通知是按照定義的順序來執行的就能夠了。 - 若是想要知道更加詳細的內容,請參閱AspectJ編程指南。
當定義在 不一樣的 切面裏的兩個通知都須要在一個相同的鏈接點中運行,那麼除非你指定,不然執行的順序是未知的。 你能夠經過指定優先級來控制執行順序。在Spring中能夠在切面類中實現 org.springframework.core.Ordered 接口作到這一點。 在兩個切面中,Ordered.getValue() 方法返回值較低的那個有更高的優先級。
引入(Introductions)(在AspectJ中被稱爲inter-type聲明)使得一個切面能夠定義被通知對象實現一個給定的接口,而且能夠表明那些對象提供具體實現。
使用 @DeclareParents註解來定義引入。這個註解被用來定義匹配的類型擁有一個新的父親。 好比,給定一個接口 UsageTracked,而後接口的具體實現 DefaultUsageTracked 類, 接下來的切面聲明瞭全部的service接口的實現都實現了 UsageTracked 接口。(好比爲了經過JMX輸出統計信息)。
@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(); } }
實現的接口經過被註解的字段類型來決定。@DeclareParents 註解的 value 屬性是一個AspectJ的類型模式:- 任何匹配類型的bean都會實現 UsageTracked 接口。 請注意,在上面的前置通知(before advice)的例子中,service beans 能夠直接用做UsageTracked 接口的實現。 若是須要編程式的來訪問一個bean,你能夠這樣寫:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
默認狀況下,在application context中每個切面都會有一個實例。 AspectJ 把這個叫作單個實例化模型(singleton instantiation model)。 也能夠用其餘的生命週期來定義切面:- Spring支持AspectJ的 perthis 和 pertarget 實例化模型 (如今還不支持percflow、percflowbelow 和 pertypewithin )。
一個"perthis" 切面的定義:在 @Aspect 註解中指定perthis 子句。 讓咱們先來看一個例子,而後解釋它是如何運做的:
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())") public class MyAspect { private int someState; @Before(com.xyz.myapp.SystemArchitecture.businessService()) public void recordServiceUsage() { // ... } }
這個perthis子句的效果是每一個獨立的service對象執行時都會建立一個切面實例(切入點表達式所匹配的鏈接點上的每個獨立的對象都會綁定到'this'上)。 service對象的每一個方法在第一次執行的時候建立切面實例。切面在service對象失效的同時失效。 在切面實例被建立前,全部的通知都不會被執行,一旦切面對象建立完成,定義的通知將會在匹配的鏈接點上執行,可是隻有當service對象是和切面關聯的才能夠。 若是想要知道更多關於per-clauses的信息,請參閱 AspectJ 編程指南。
'pertarget'實例模型的跟「perthis」徹底同樣,只不過是爲每一個匹配於鏈接點的獨立目標對象建立一個切面實例。
如今你已經看到了每一個獨立的部分是如何運做的了,是時候把他們放到一塊兒作一些有用的事情了!
由於樂觀鎖的關係,有時候business services可能會失敗(有人甚至在一開始運行事務的時候就失敗了)。若是從新嘗試一下,頗有可能就會成功。 對於business services來講,重試幾回是很正常的(Idempotent操做不須要用戶參與,不然會得出矛盾的結論) 咱們可能須要透明的重試操做以免讓客戶看見 OptimisticLockingFailureException 例外被拋出。 很明顯,在一個橫切多層的狀況下,這是很是有必要的,所以經過切面來實現是很理想的。
由於咱們想要重試操做,咱們會須要使用到環繞通知,這樣咱們就能夠屢次調用proceed()方法。下面是簡單的切面實現:
請注意切面實現了 Ordered 接口,這樣咱們就能夠把切面的優先級設定爲高於事務通知(咱們每次重試的時候都想要在一個全新的事務中進行)。 maxRetries 和 order 屬性均可以在Spring中配置。 主要的動做在 doOptimisticOperation 這個環繞通知中發生。 請注意這個時候咱們全部的 businessService() 方法都會使用這個重試策略。 咱們首先會嘗試處理,而後若是咱們獲得一個OptimisticLockingFailureException 意外,咱們只須要簡單的重試,直到咱們耗盡全部預設的重試次數。
對應的Spring配置以下:
爲了改進切面,使之僅僅重試idempotent操做,咱們能夠定義一個 Idempotent 註解:
而且對service操做的實現進行註解。 這樣若是你只但願改變切面使得idempotent的操做會嘗試屢次,你只須要改寫切入點表達式,這樣只有 @Idempotent 操做會匹配:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " + "@annotation(com.xyz.myapp.service.Idempotent)") public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable { ... }
若是你沒法使用Java 5,或者你比較喜歡使用XML格式,Spring2.0也提供了使用新的"aop"命名空間來定義一個切面。 和使用@AspectJ風格徹底同樣,切入點表達式和通知類型一樣獲得了支持,所以在這一節中咱們將着重介紹新的 語法 和回顧前面咱們所討論的如何寫一個切入點表達式和通知參數的綁定等等(Section 6.2, 「@AspectJ支持」)。
使用本章所介紹的aop命名空間標籤(aop namespace tag),你須要引入Appendix A, XML Schema-based configuration中說起的spring-aop schema。 參見Section A.2.6, 「The aop schema」。
在Spring的配置文件中,全部的切面和通知器都必須定義在 <aop:config> 元素內部。 一個application context能夠包含多個<aop:config>。 一個 <aop:config> 能夠包含pointcut,advisor和aspect元素,而且三者必須按照這樣的順序進行聲明。
有了schema的支持,切面就和常規的Java對象同樣被定義成application context中的一個bean。 對象的字段和方法提供了狀態和行爲信息,XML文件則提供了切入點和通知信息。
切面使用<aop:aspect>來聲明,backing bean(支持bean)經過 ref 屬性來引用:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> ... </aop:aspect> </aop:config> <bean id="aBean" class="..."> ... </bean>
切面的支持bean(上例中的"aBean")能夠象其餘Spring bean同樣被容器管理配置以及依賴注入。
切入點能夠在切面裏面聲明,這種狀況下切入點只在切面內部可見。切入點也能夠直接在<aop:config>下定義,這樣就可使多個切面和通知器共享該切入點。
一個描述service層中表示全部service執行的切入點能夠以下定義:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> </aop:config>
注意切入點表達式自己使用了 Section 6.2, 「@AspectJ支持」 中描述的AspectJ 切入點表達式語言。 若是你在Java 5環境下使用基於schema的聲明風格,可參考切入點表達式類型中定義的命名式切入點,不過這在JDK1.4及如下版本中是不被支持的(由於依賴於Java 5中的AspectJ反射API)。 因此在JDK 1.5中,上面的切入點的另一種定義形式以下:
<aop:config> <aop:pointcut id="businessService" expression="com.xyz.myapp.SystemArchitecture.businessService()"/> </aop:config>
假定你有 Section 6.2.3.3, 「共享常見的切入點(pointcut)定義」中說描述的 SystemArchitecture 切面。
在切面裏面聲明一個切入點和聲明一個頂級的切入點很是相似:
<aop:config> <aop:aspect id="myAspect" ref="aBean"> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> ... </aop:aspect> </aop:config>
當須要鏈接子表達式的時候,'&&'在XML中用起來很是不方便,因此關鍵字'and', 'or' 和 'not'能夠分別用來代替'&&', '||' 和 '!'。
注意這種方式定義的切入點經過XML id來查找,而且不能定義切入點參數。在基於schema的定義風格中命名切入點支持較之@AspectJ風格受到了不少的限制。
和@AspectJ風格同樣,基於schema的風格也支持5種通知類型而且二者具備一樣的語義。
Before通知在匹配方法執行前進入。在<aop:aspect>裏面使用<aop:before>元素進行聲明。
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
這裏 dataAccessOperation 是一個頂級(<aop:config>)切入點的id。 要定義內置切入點,可將 pointcut-ref 屬性替換爲pointcut 屬性:
<aop:aspect id="beforeExample" ref="aBean"> <aop:before pointcut="execution(* com.xyz.myapp.dao.*.*(..))" method="doAccessCheck"/> ... </aop:aspect>
咱們已經在@AspectJ風格章節中討論過了,使用命名切入點可以明顯的提升代碼的可讀性。
Method屬性標識了提供了通知的主體的方法(doAccessCheck)。這個方法必須定義在包含通知的切面元素所引用的bean中。 在一個數據訪問操做執行以前(執行鏈接點和切入點表達式匹配),切面中的"doAccessCheck"會被調用。
After returning通知在匹配的方法徹底執行後運行。和Before通知同樣,能夠在<aop:aspect>裏面聲明。例如:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" method="doAccessCheck"/> ... </aop:aspect>
和@AspectJ風格同樣,通知主體能夠接收返回值。使用returning屬性來指定接收返回值的參數名:
<aop:aspect id="afterReturningExample" ref="aBean"> <aop:after-returning pointcut-ref="dataAccessOperation" returning="retVal" method="doAccessCheck"/> ... </aop:aspect>
doAccessCheck方法必須聲明一個名字叫 retVal 的參數。 參數的類型強制匹配,和先前咱們在@AfterReturning中講到的同樣。例如,方法簽名能夠這樣聲明:
public void doAccessCheck(Object retVal) {...
After throwing通知在匹配方法拋出異常退出時執行。在 <aop:aspect> 中使用after-throwing元素來聲明:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" method="doRecoveryActions"/> ... </aop:aspect>
和@AspectJ風格同樣,能夠從通知體中獲取拋出的異常。 使用throwing屬性來指定異常的名稱,用這個名稱來獲取異常:
<aop:aspect id="afterThrowingExample" ref="aBean"> <aop:after-throwing pointcut-ref="dataAccessOperation" thowing="dataAccessEx" method="doRecoveryActions"/> ... </aop:aspect>
doRecoveryActions方法必須聲明一個名字爲 dataAccessEx 的參數。 參數的類型強制匹配,和先前咱們在@AfterThrowing中講到的同樣。例如:方法簽名能夠以下這般聲明:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
After (finally)通知在匹配方法退出後執行。使用 after 元素來聲明:
<aop:aspect id="afterFinallyExample" ref="aBean"> <aop:after pointcut-ref="dataAccessOperation" method="doReleaseLock"/> ... </aop:aspect>
Around通知是最後一種通知類型。Around通知在匹配方法運行期的「周圍」執行。 它有機會在目標方法的前面和後面執行,並決定何時運行,怎麼運行,甚至是否運行。 Around通知常常在須要在一個方法執行前或後共享狀態信息,而且是線程安全的狀況下使用(啓動和中止一個計時器就是一個例子)。 注意選擇能知足你需求的最簡單的通知類型(i.e.若是簡單的before通知就能作的事情絕對不要使用around通知)。
Around通知使用 aop:around 元素來聲明。 通知方法的第一個參數的類型必須是 ProceedingJoinPoint 類型。 在通知的主體中,調用 ProceedingJoinPoint的proceed() 方法來執行真正的方法。 proceed 方法也可能會被調用而且傳入一個 Object[] 對象 - 該數組將做爲方法執行時候的參數。 參見 Section 6.2.4.5, 「環繞通知(Around Advice)」 中提到的一些注意點。
<aop:aspect id="aroundExample" ref="aBean"> <aop:around pointcut-ref="businessService" method="doBasicProfiling"/> ... </aop:aspect>
doBasicProfiling 通知的實現和@AspectJ中的例子徹底同樣(固然要去掉註解):
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
Schema-based聲明風格和@AspectJ支持同樣,支持通知的全名形式 - 經過通知方法參數名字來匹配切入點參數。 參見Section 6.2.4.6, 「通知參數(Advice parameters)」 獲取詳細信息。
若是你但願顯式指定通知方法的參數名(而不是依靠先前說起的偵測策略),能夠經過 arg-names 屬性來實現。示例以下:
<aop:before pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)" method="audit" arg-names="auditable"/>
The arg-names attribute accepts a comma-delimited list of parameter names.
arg-names屬性接受由逗號分割的參數名列表。
當同一個切入點(執行方法)上有多個通知須要執行時,執行順序規則在 Section 6.2.4.7, 「通知(Advice)順序」 已經說起了。 切面的優先級經過切面的支持bean是否實現了Ordered接口來決定。
Intrduction (在AspectJ中成爲inter-type聲明)容許一個切面聲明一個通知對象實現指定接口,而且提供了一個接口實現類來表明這些對象。
在 aop:aspect 內部使用 aop:declare-parents 元素定義Introduction。 該元素用於用來聲明所匹配的類型有了一個新的父類型(因此有了這個名字)。 例如,給定接口 UsageTracked,以及這個接口的一個實現類 DefaultUsageTracked, 下面聲明的切面全部實現service接口的類同時實現 UsageTracked 接口。(好比爲了經過JMX暴露statistics。)
<aop:aspect id="usageTrackerAspect" ref="usageTracking"> <aop:declare-parents types-matching="com.xzy.myapp.service.*+", implement-interface="UsageTracked" default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/> <aop:before pointcut="com.xyz.myapp.SystemArchitecture.businessService() and this(usageTracked)" method="recordUsage"/> </aop:aspect>
The class backing the usageTracking bean would contain the method:
usageTracking bean的支持類能夠包含下面的方法:
public void (UsageTracked usageTracked) { usageTracked.incrementUseCount(); }
欲實現的接口由 implement-interface 屬性來指定。 types-matching 屬性的值是一個AspectJ類型模式:- 任何匹配類型的bean會實現 UsageTracked 接口。 注意在Before通知的例子中,srevice bean能夠用做 UsageTracked 接口的實現。 若是編程形式訪問一個bean,你能夠這樣來寫:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
"advisors"這個概念來自Spring1.2對AOP的支持,在AspectJ中是沒有等價的概念。 advisor就像一個小的自包含的切面,這個切面只有一個通知。 切面自身經過一個bean表示,而且必須實現一個通知接口, 在 Section 7.3.2, 「Spring裏的通知類型」 中咱們會討論相應的接口。Advisors能夠很好的利用AspectJ切入點表達式。
Spring 2.0 經過 <aop:advisor> 元素來支持advisor 概念。 你將會發現它大多數狀況下會和transactional advice一塊兒使用,transactional advice在Spring 2.0中有本身的命名空間。格式以下:
<aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
和在上面使用的 pointcut-ref 屬性同樣,你還可使用 pointcut 屬性來定義一個內聯的切入點表達式。
爲了定義一個advisord的優先級以便讓通知能夠有序,使用 order 屬性來定義 advisor的值 Ordered 。
讓咱們來看看在 Section 6.2.7, 「例子」 提過樂觀鎖失敗重試的例子,若是使用schema對這個例子進行重寫是什麼效果。
由於樂觀鎖的關係,有時候business services可能會失敗(有人甚至在一開始運行事務的時候就失敗了)。 若是從新嘗試一下,頗有可能就會成功。對於business services來講,重試幾回是很正常的(Idempotent操做不須要用戶參與,不然會得出矛盾的結論) 咱們可能須要透明的重試操做以免讓客戶看見 OptimisticLockingFailureException 例外被拋出。 很明顯,在一個橫切多層的狀況下,這是很是有必要的,所以經過切面來實現是很理想的。
由於咱們想要重試操做,咱們會須要使用到環繞通知,這樣咱們就能夠屢次調用proceed()方法。 下面是簡單的切面實現(只是一個schema支持的普通Java 類):
public class OptimisticOperationExecutor 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; } public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable { int numAttempts = 0; OptimisticLockingFailureException lockFailureException; do { numAttempts++; try { return pjp.proceed(); } catch(OptimisticLockingFailureException ex) { lockFailureException = ex; } } while(numAttempts <= this.maxRetries); throw lockFailureException; } }
請注意切面實現了 Ordered 接口,這樣咱們就能夠把切面的優先級設定爲高於事務通知(咱們每次重試的時候都想要在一個全新的事務中進行)。 maxRetries 和 order 屬性均可以在Spring中配置。 主要的動做在 doOptimisticOperation 這個環繞通知中發生。 請注意這個時候咱們全部的 businessService() 方法都會使用這個重試策略。 咱們首先會嘗試處理,而後若是咱們獲得一個OptimisticLockingFailureException 異常,咱們只須要簡單的重試,直到咱們耗盡全部預設的重試次數。
這個類跟咱們在@AspectJ的例子中使用的是相同的,只是沒有使用註解。對應的Spring配置以下:
<aop:config> <aop:aspect id="optimisticOperationRetry" ref="optimisticOperationExecutor"> <aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..))"/> <aop:around pointcut-ref="idempotentOperation" method="doOptimisticOperation"/> </aop:aspect> </aop:config> <bean id="optimisticOperationExecutor" class="com.xyz.myapp.service.impl.OptimisticOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>
請注意咱們如今假設全部的bussiness services都是idempotent。若是不是這樣,咱們能夠改寫切面,加上 Idempotent 註解,讓它只調用idempotent:
@Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation }
而且對service操做的實現進行註解。這樣若是你只但願改變切面使得idempotent的操做會嘗試屢次,你只須要改寫切入點表達式,這樣只有 @Idempotent 操做會匹配:
<aop:pointcut id="idempotentOperation" expression="execution(* com.xyz.myapp.service.*.*(..)) and @annotation(com.xyz.myapp.service.Idempotent)"/>
咱們徹底能夠混合使用如下幾種風格的切面定義:使用自動代理的@AspectJ 風格的切面,schema-defined <aop:aspect> 的切面,和用 <aop:advisor> 聲明的advisor,甚至是使用Spring 1.2風格的代理和攔截器。 因爲以上幾種風格的切面定義的都使用了相同的底層機制,所以能夠很好的共存。
Spring AOP部分使用JDK動態代理或者CGLIB來爲目標對象建立代理。(建議儘可能使用JDK的動態代理)
若是被代理的目標對象實現了至少一個接口,則會使用JDK動態代理。全部該目標類型實現的接口都將被代理。若該目標對象沒有實現任何接口,則建立一個CGLIB代理。
若是你但願強制使用CGLIB代理,(例如:但願代理目標對象的全部方法,而不僅是實現自接口的方法)那也能夠。可是須要考慮如下問題:
沒法通知(advise)Final 方法,由於他們不能被覆寫。
你須要將CGLIB 2二進制發行包放在classpath下面,與之相較JDK自己就提供了動態代理
強制使用CGLIB代理須要將 <aop:config> 的 proxy-target-class 屬性設爲true:
<aop:config proxy-target-class="true">
...
</aop:config>
請注意這個屬性的設置僅對 每個<aop-config/> 有效; 你可能有多個 <aop-config/>,其中有的強制使用CGLIB代理,有的沒有。
當須要使用CGLIB代理和@AspectJ自動代理支持,請按照以下的方式設置 <aop:aspectj-autoproxy> 的 proxy-target-class 屬性:
<aop:aspectj-autoproxy proxy-target-class="true"/>
除了在配置文件中使用 <aop:config> 或者 <aop:aspectj-autoproxy> 來聲明切面。 一樣能夠經過編程方式來建立代理通知(advise)目標對象。關於Spring AOP API的詳細介紹,請參看下一章。這裏咱們重點介紹自動建立代理。
類 org.springframework.aop.aspectj.annotation.AspectJProxyFactory 能夠爲@AspectJ切面的目標對象建立一個代理。該類的基本用法很是簡單,示例以下。請參看Javadoc獲取更詳細的信息。
// create a factory that can generate a proxy for the given target object AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // add an aspect, the class must be an @AspectJ aspect // you can call this as many times as you need with different aspects factory.addAspect(SecurityManager.class); // you can also add existing aspect instances, the type of the object // supplied must be an @AspectJ aspect factory.addAspect(usageTracker); // now get the proxy object... MyInterfaceType proxy = factory.getProxy();
到目前爲止本章討論的一直是純Spring AOP。 在這一節裏面咱們將介紹如何使用AspectJ compiler/weaver來代替Spring AOP或者做爲它的補充,由於有些時候Spring AOP單獨提供的功能也許並不能知足你的須要。
Spring提供了一個小巧的AspectJ aspect library (你能夠在程序發行版本中單獨使用 spring-aspects.jar 文件,並將其加入到classpath下以使用其中的切面)。 Section 6.7.1, 「在Spring中使用AspectJ來爲domain object進行依賴注入」 和Section 6.7.2, 「Spring中其餘的AspectJ切面」 討論了該庫和如何使用該庫。 Section 6.7.3, 「使用Spring IoC來配置AspectJ的切面」 討論瞭如何對經過AspectJ compiler織入的AspectJ切面進行依賴注入。 最後Section 6.7.4, 「在Spring應用中使用AspectJ Load-time weaving(LTW)」介紹了使用AspectJ的Spring應用程序如何裝載期織入(load-time weaving)。
Spring容器對application context中定義的bean進行實例化和配置。 一樣也能夠經過bean factory來爲一個已經存在且已經定義爲spring bean的對象應用所包含的配置信息。 spring-aspects.jar中包含了一個annotation-driven的切面,提供了能爲任何對象進行依賴注入的能力。 這樣的支持旨在爲 脫離容器管理 建立的對象進行依賴注入。 Domain object常常處於這樣的情形:它們多是經過 new 操做符建立的對象, 也多是ORM工具查詢數據庫的返回結果對象。
包 org.springframework.orm.hibernate.support 中的類 DependencyInjectionInterceptorFactoryBean 可讓Spring爲Hibernate建立而且配置prototype類型的domain object(使用自動裝配或者確切命名的bean原型定義)。 固然,攔截器不支持配置你編程方式建立的對象而非檢索數據庫返回的對象。 其餘framework也會提供相似的技術。還是那句話,Be Pragramatic選擇能知足你需求的方法中最簡單的那個。 請注意前面說起的類 沒有 隨Spring發行包一塊兒發佈。 若是你但願使用該類,須要從Spring CVS Respository上下載而且自行編譯。 你能夠在Spring CVS respository下的 'sandbox' 目錄下找到該文件。
@Configurable 註解標記了一個類能夠經過Spring-driven方式來配置。 在最簡單的狀況下,咱們只把它看成標記註解:
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation; @Configurable public class Account { ...
當只是簡單地做爲一個標記接口來使用的時候,Spring將採用和該已註解的類型(好比Account類)全名 (com.xyz.myapp.domain.Account)一致的bean原型定義來配置一個新實例。 因爲一個bean默認的名字就是它的全名,因此一個比較方便的辦法就是省略定義中的id屬性:
<bean class="com.xyz.myapp.domain.Account" singleton="false"> <property name="fundsTransferService" ref="fundsTransferService"/> ... </bean>
若是你但願明確的指定bean原型定義的名字,你能夠在註解中直接定義:
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation; @Configurable("account") public class Account { ...
Spring會查找名字爲"account"的bean定義,並使用它做爲原型定義來配置一個新的Account對象。
你也可使用自動裝配來避免手工指定原型定義的名字。 只要設置 @Configurable 註解中的autowire屬性就可讓Spring來自動裝配了: @Configurable(autowire=Autowire.BY_TYPE) 或者 @Configurable(autowire=Autowire.BY_NAME,這樣就能夠按類型或者按名字自動裝配了。
最後,你能夠設置 dependencyCheck 屬性,經過設置,Spring對新建立和配置的對象的對象引用進行校驗 (例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true) )。 若是這個屬性被設爲true,Spring會在配置結束後校驗除了primitives和collections類型的全部的屬性是否都被賦值了。
僅僅使用註解並無作任何事情。但當註解存在時,spring-aspects.jar中的 AnnotationBeanConfigurerAspect 就起做用了。 實質上切面作了這些:當初始化一個有 @Configurable 註解的新對象時,Spring按照註解中的屬性來配置這個新建立的對象。 要實現上述的操做,已註解的類型必須由AspectJ weaver來織入 - 你可使用一個 build-time ant/maven任務來完成 (參見AspectJ Development Environment Guide) 或者使用load-time weaving(參見 Section 6.7.4, 「在Spring應用中使用AspectJ Load-time weaving(LTW)」)。
類 AnnotationBeanConfigurerAspect 自己也須要Spring來配置(得到bean factory的引用,使用bean factory配置新的對象)。 爲此Spring AOP命名空間定義了一個很是方便的標籤。以下所示,能夠很簡單的在application context配置文件包含這個標籤中。
<aop:spring-configured/>
若是你使用DTD代替Schema,對應的定義以下:
<bean class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" factory-method="aspectOf"/>
在切面配置完成 以前 建立的@Configurable對象實例會致使在log中留下一個warning,而且任何對於該對象的配置都不會生效。 舉一個例子,一個Spring管理配置的bean在被Spring初始化的時候建立了一個domain object。 對於這樣的狀況,你須要定義bean屬性中的"depends-on"屬性來手動指定該bean依賴於configuration切面。
<bean id="myService" class="com.xzy.myapp.service.MyService" depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"> ... </bean>
提供 @Configurable 支持的一個目的就是使得domain object的單元測試能夠獨立進行,不須要經過硬編碼查找各類倚賴關係。 若是@Configurable 類型沒有經過AspectJ織入, 則在單元測試過程當中註解不會起到任何做用,測試中你能夠簡單的爲對象的mock或者stub屬性賦值,而且和正常狀況同樣的去使用該對象。 若是 @Configurable 類型經過AspectJ織入, 咱們依然能夠脫離容器進行單元測試,不過每次建立一個新的 @Configurable 對象時都會看到一個warning標示該對象不受Spring管理配置。
AnnotationBeanConfigurerAspect 經過一個AspectJ singleton切面來實現對 @Configurable 的支持。 一個singleton切面的做用域和一個靜態變量的做用域是同樣的,例如,對於每個classloader有一個切面來定義類型。 這就意味着若是你在一個classloader層次結構中定義了多個application context的時候就須要考慮在哪裏定義 <aop:spring-configured/> bean和在哪一個classpath下放置Srping-aspects.jar。
考慮一下典型的Spring web項目,通常都是由一個父application context定義大部分business service和所須要的其餘資源,而後每個servlet擁有一個子application context定義。 全部這些context共存於同一個classloader hierarchy下,所以對於全體context,AnnotationBeanConfigurerAspect 僅能夠維護一個引用。 在這樣的狀況下,咱們推薦在父application context中定義<aop:spring-configured/> bean: 這裏所定義的service多是你但願注入domain object的。 這樣作的結果是你不能爲子application context中使用@Configurable的domain object配置bean引用(可能你也根本就不但願那麼作!)。
當在一個容器中部署多個web-app的時候,請確保每個web-application使用本身的classloader來加載spring-aspects.jar中的類(例如將spring-aspects.jar放在WEB-INF/lib目錄下)。 若是spring-aspects.jar被放在了容器的classpath下(所以也被父classloader加載),則全部的web application將共享一個aspect實例,這可能並非你所想要的。
除了 @Configurable 支持,spring-aspects.jar包含了一個AspectJ切面能夠用來爲那些使用了 @Transactional annotation 的類型和方法驅動Spring事務管理(參見 Chapter 9, 事務管理)。 提供這個的主要目的是有些用戶但願脫離Spring容器使用Spring的事務管理。
對於AspectJ程序員,但願使用Spring管理配置和事務管理支持,不過他們不想(或者不能)使用註解,spring-aspects.jar也包含了一些抽象切面供你繼承來提供你本身的切入點定義。 參見 AbstractBeanConfigurerAspect 和 AbstractTransactionAspect 的Javadoc獲取更多信息。 做爲一個例子,下面的代碼片段展現瞭如何編寫一個切面,而後經過bean原型定義中和類全名匹配的來配置domian object中全部的實例:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect { public DomainObjectConfiguration() { setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver()); } /** * The creation of a new bean (any object in the domain model) */ protected pointcut beanCreation(Object beanInstance) : initialization(new(..)) && SystemArchitecture.inDomainModel() && this(beanInstance); }
當在Spring application中使用AspectJ的時候,很天然的會想到用Spring來管理這些切面。 AspectJ runtime自身負責切面的建立,這意味着經過Spring來管理AspectJ 建立切面依賴於切面所使用的AspectJ instantiation model(per-clause)。
大多數AspectJ切面都是 singleton 切面。 管理這些切面很是容易,和一般同樣建立一個bean定義引用該切面類型就能夠了,而且在bean定義中包含 factory-method="aspectOf" 這個屬性。 這確保Spring從AspectJ獲取切面實例而不是嘗試本身去建立該實例。示例以下:
<bean id="profiler" class="com.xyz.profiler.Profiler" factory-method="aspectOf"> <property name="profilingStrategy" ref="jamonProfilingStrategy"/> </bean>
對於non-singleton的切面,最簡單的配置管理方法是定義一個bean原型定義而且使用@Configurable支持,這樣就能夠在切面被AspectJ runtime建立後管理它們。
若是你但願一些@AspectJ切面使用AspectJ來織入(例如使用load-time織入domain object) 和另外一些@AspectJ切面使用Spring AOP,而這些切面都是由Spring來管理的,那你就須要告訴Spring AOP @AspectJ自動代理支持那些切面須要被自動代理。 你能夠經過在 <aop:aspectj-autoproxy> 聲明中使用一個或多個 include。 每個指定了一種命名格式,只有bean命名至少符合其中一種狀況下才會使用Spring AOP自動代理配置:
<aop:aspectj-autoproxy> <include name="thisBean"/> <include name="thatBean"/> </aop:aspectj-autoproxy>
Load-time weaving(LTW)指的是在虛擬機載入字節碼文件時動態織入AspectJ切面。 關於LTW的詳細信息,請查看 LTW section of the AspectJ Development Environment Guide。 在這裏咱們重點來看一下Java 5環境下Spring應用如何配置LTW。
LTW須要定義一個 aop.xml,並將其置於META-INF目錄。 AspectJ會自動查找全部可見的classpath下的META-INF/aop.xml文件,而且經過定義內容的合集來配置自身。
一個基本的META-INF/aop.xml文件應該以下所示:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver> <include within="com.xyz.myapp..*"/> </weaver> </aspectj>
'include'的內容告訴AspectJ那些類型須要被歸入織入過程。使用包名前綴並加上"..*"(表示該子包中的全部類型)是一個不錯的默認設定。 使用include元素是很是重要的,否則AspectJ會查找每個應用裏面用到的類型(包括Spring的庫和其它許多相關庫)。一般你並不但願織入這些類型而且不肯意承擔AspectJ嘗試去匹配的開銷。
但願在日誌中記錄LTW的活動,請添加以下選項:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver options="-showWeaveInfo, -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler"> <include within="com.xyz.myapp..*"/> </weaver> </aspectj>
最後,若是但願精確的控制使用哪些切面,可使用 aspects。 默認狀況下全部定義的切面都將被織入(spring-aspects.jar包含了META-INF/aop.xml,定義了配置管理和事務管理切面)。 若是你在使用spring-aspects.jar,可是隻但願使用配製管理切面而不須要事務管理的話,你能夠像下面那樣定義:
在Java 5平臺下,LTW能夠經過虛擬機的參數來啓用。
-javaagent:<path-to-ajlibs>/aspectjweaver.jar
更多關於AspectJ的信息能夠查看 AspectJ home page。
Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley, 2005)全面介紹並提供了AspectJ語言參考。
AspectJ in Action by Ramnivas Laddad (Manning, 2003)是一本很是出色介紹AOP的書籍;全書着重介紹了AspectJ,但也對一些通用的AOP場景進行了比較深刻的研究。