使用Spring進行面向切面編程(AOP)

簡介

面向切面編程AOP)提供另外一種角度來思考程序結構,通過這種方式彌補了面向對象編程(OOP)的不足。 除了類(classes)以外,AOP提供了 切面。切面對關注點進行模塊化,例如橫切多個類型和對象的事務管理。 (這些關注點術語通常稱作 橫切(crosscutting) 關注點。)

Spring的一個關鍵的組件就是 AOP框架。 儘管如此,Spring IoC容器並不依賴於AOP,這意味着你可以自由選擇是否使用AOP,AOP提供強大的中間件解決方案,這使得Spring IoC容器更加完善。

Spring中所使用的AOP:

  • 提供聲明式企業服務,特別是爲了替代EJB聲明式服務。 最重要的服務是 聲明性事務管理(declarative transaction management) , 這個服務建立在Spring的抽象事務管理(transaction abstraction)之上。

  • 允許用戶實現自定義的切面,用AOP來完善OOP的使用。

這樣你可以把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通知可以被應用到一組橫跨多個對象中的方法上(例如服務層的所有業務操作)。

6.1.2. Spring AOP的功能和目標

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。

6.1.3. Spring的AOP代理

Spring缺省使用J2SE 動態代理(dynamic proxies)來作爲AOP的代理。這樣任何接口都可以被代理。

Spring也支持使用CGLIB代理. 對於需要代理類而不是代理接口的時候CGLIB代理是很有必要的。 如果一個業務對象並沒有實現一個接口,默認就會使用CGLIB。 此外,面向接口編程 也是一個最佳實踐,業務對象通常都會實現一個或多個接口。

此外,還可以強制的使用CGLIB:我們將會在以後討論這個問題,解釋問什麼你會要這麼做。

在Spring 2.0之後,Spring可能會提供多種其他類型的AOP代理,包括了完整的生成類。這不會影響到編程模型。

6.2. @AspectJ支持

如果你使用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 目錄下找到。

6.2.2. 聲明一個切面

在啓用@AspectJ支持的情況下,在application context中定義的任意帶有一個@Aspect切面(擁有@Aspect註解)的bean都將被Spring自動識別並用於配置在Spring AOP。 以下例子展示了爲了完成一個不是非常有用的切面所需要的最小定義:

下面是在application context中的一個常見的bean定義,這個bean指向一個使用了 @Aspect 註解的bean類:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">    <!-- configure properties of aspect here as normal --> </bean>

下面是 NotVeryUsefulAspect 類定義,使用了 org.aspectj.lang.annotation.Aspect 註解。

 

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}


切面(用 @Aspect 註解的類)和其他類一樣有方法和字段定義。他們也可能包括切入點,通知和引入(inter-type)聲明。

6.2.3. 聲明一個切入點(pointcut)

回想一下,切入點決定了連接點關注的內容,使得我們可以控制通知什麼執行。 Spring AOP只支持Spring bean方法執行連接點。所以你可以把切入點看做是匹配Spring bean上的方法執行。 一個切入點聲明有兩個部分:一個包含名字和任意參數的簽名,還有一個切入點表達式,該表達式決定了我們關注那個方法的執行。 在@AspectJ中,一個切入點實際就是一個普通的方法定義提供的一個簽名,並且切入點表達式使用 @Pointcut註解來表示。 這個方法的返回類型必須是 void。 如下的例子定義了一個切入點'transfer',這個切入點匹配了任意名爲"transfer"的方法執行:

@Pointcut("execution(* transfer(..))")     private void transfer() {}

切入點表達式,也就是 @Pointcut 註解的值,是正規的AspectJ 5切入點表達式。 如果你想要更多瞭解AspectJ的 切入點語言,請參見 AspectJ 編程指南(如果要了解基於Java 5的擴展請參閱 AspectJ 5 開發手冊) 或者其他人寫的關於AspectJ的書,例如Colyer et. al.著的《Eclipse AspectJ》或者Ramnivas Laddad著的《AspectJ in Action》。

6.2.3.1. 支持的切入點指定者

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的時候方法的執行),其中連接點的主題有某種給定的註解。

因爲Spring AOP限制了連接點必須是方法執行級別的,pointcut designators的討論也給出了一個定義,這個定義和AspectJ的編程指南中的定義相比顯得更加狹窄。 除此之外,AspectJ它本身有基於類型的語義,在執行的連接點'this'和'target'都是指同一個對象,也就是執行方法的對象。 Spring AOP是一個基於代理的系統,並且嚴格區分代理對象本身(對應於'this')和背後的目標對象(對應於'target')

6.2.3.2. 合併切入點表達式

切入點表達式可以使用using '&&', '||' 和 '!'來合併.還可以通過名字來指向切入點表達式。 以下的例子展示了三種切入點表達式: anyPublicOperation(在一個方法執行連接點代表了任意public方法的執行時匹配); inTrading(在一個代表了在交易模塊中的任意的方法執行時匹配) 和 tradingOperation(在一個代表了在交易模塊中的任意的公共方法執行時匹配)。

@Pointcut("execution(public * *(..))")     private void anyPublicOperation() {}     @Pointcut("within(com.xyz.someapp.trading..*")     private void inTrading() {}     @Pointcut("anyPublicOperation() && inTrading()")     private void tradingOperation() {}

就上所示的,從更小的命名組件來構建更加複雜的切入點表達式是一種最佳實踐。 當用名字來指定切入點時使用的是常見的Java成員可視性訪問規則。 (比如說,你可以在同一類型中訪問私有的切入點,在繼承關係中訪問受保護的切入點,可以在任意地方訪問公共切入點。 成員可視性訪問規則不影響到切入點的 匹配

在AspectJ 1.5.1中有一個bug (#140357)有時候可能會導致Spring所使用的AspectJ切入點解析失敗, 即使用一個已命名的切入點來引用到另一個同類型的切入點的時候。 在AspectJ的開發中已經解決這個bug,可以在AspectJ的下載頁面得到。在1.5.2發佈時將會包含這一fix。 如果你遇到了這個問題,你可以去下載AspectJ的開發構建包,並且更新你的 aspectjweaver.jar,這是在AspectJ 1.5.2發佈前的臨時解決方案。

6.2.3.3. 共享常見的切入點(pointcut)定義

當開發企業級應用的時候,你通常會想要從幾個切面來參考模塊化的應用和特定操作的集合。 我們推薦定義一個“SystemArchitecture”切面來捕捉常見的切入點表達式。一個典型的切面可能看起來像下面這樣:

 

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

  /**
   * A join point is in the web layer if the method is defined
   * in a type in the com.xyz.someapp.web package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  /**
   * A join point is in the service layer if the method is defined
   * in a type in the com.xyz.someapp.service package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  /**
   * A join point is in the data access layer if the method is defined
   * in a type in the com.xyz.someapp.dao package or any sub-package
   * under that.
   
*/

  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  /**
   * A business service is the execution of any method defined on a service
   * interface. This definition assumes that interfaces are placed in the
   * "service" package, and that implementation types are in sub-packages.
   *
   * If you group service interfaces by functional area (for example,
   * in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
   * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
   * could be used instead.
   
*/

  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}

  /**
   * A data access operation is the execution of any method defined on a
   * dao interface. This definition assumes that interfaces are placed in the
   * "dao" package, and that implementation types are in sub-packages.
   
*/

  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}


示例中的切入點定義一個你可以在任何需要切入點表達式的地方可引用的切面。比如,爲了使service層事務化,你可以寫: 

<aop:config>
  <aop:advisor
      
pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
      advice-ref
="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
<tx:attributes>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>



在 Section 6.3, “Schema-based AOP support” 中討論 <aop:config> 和 <aop:advisor>標籤。 在 Chapter 9, 事務管理 中討論事務標籤。

6.2.3.4. 示例

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對象可以在通知體內訪問到的部分。

6.2.4. 聲明通知

通知是跟一個切入點表達式關聯起來的,並且在切入點匹配的方法執行之前或者之後或者之前和之後運行。 切入點表達式可能是指向已命名的切入點的簡單引用或者是一個已經聲明過的切入點表達式。

6.2.4.1. 前置通知(Before advice)

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

6.2.4.2. 返回後通知(After returning advice)

返回後通知通常在一個匹配的方法返回的時候執行。使用 @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 類,也就是說返回任意類型都會匹配)

6.2.4.3. 拋出後通知(After throwing advice)

拋出後通知在一個方法拋出異常後執行。使用 @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)。

6.2.4.4. 後通知(After (finally) advice)

不論一個方法是如何結束的,在它結束後(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() {
// ...
}
}

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.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可能在通知體內部被調用一次,許多次,或者根本不被調用。

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使用如下的策略來決定參數名字:

  1. 如果參數名字已經被用戶明確指定,則使用指定的參數名: 通知(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 參數了,因爲編譯器會自動完成這一工作。
  2. 使用 'argNames' 屬性有點不那麼優雅,所以如果沒有指定'argNames' 屬性, Spring AOP 會尋找類的debug信息,並且嘗試從本地變量表(local variable table)中來決定參數名字。 只要編譯的時候使用了debug信息(至少要使用 '-g:vars' ),就可獲得這些信息。 使用這個flag編譯的結果是: (1)你的代碼將能夠更加容易的讀懂(反向工程), (2)生成的class文件會稍許大一些(不重要的), (3)移除不被使用的本地變量的優化功能將會失效。 換句話說,你在使用這個flag的時候不會遇到任何困難。

  3. 如果不加上debug信息來編譯的話,Spring AOP將會嘗試推斷參數的綁定。 (例如,要是隻有一個變量被綁定到切入點表達式(pointcut expression)、通知方法(advice method)將會接受這個參數, 這是顯而易見的)。 如果變量的綁定不明確,將會拋出一個 AmbiguousBindingException 異常。

  4. 如果以上所有策略都失敗了,將會拋出一個 IllegalArgumentException 異常。

6.2.4.6.4. 處理參數

我們之前提過我們將會討論如何編寫一個 帶參數的 的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});
}

大多數情況下你都會這樣綁定(就像上面的例子那樣)。

6.2.4.7. 通知(Advice)順序

如果有多個通知想要在同一連接點運行會發生什麼?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的方法的執行, doBeforeOnedoBeforeTwodoAfterOne 和 doAfterTwo 通知方法都需要運行。 執行順序將按照聲明的順序來確定。在這個例子中,執行的結果會是:

doBeforeOne
doBeforeTwo
foo
doAfterOne
doAfterTwo

換言之,因爲doBeforeOne先定義,它會先於doBeforeTwo執行,而doAfterTwo後於doAfterOne定義,所以它會在doAfterOne之後執行。 只需要記住通知是按照定義的順序來執行的就可以了。 - 如果想要知道更加詳細的內容,請參閱AspectJ編程指南。

當定義在 不同的 切面裏的兩個通知都需要在一個相同的連接點中運行,那麼除非你指定,否則執行的順序是未知的。 你可以通過指定優先級來控制執行順序。在Spring中可以在切面類中實現 org.springframework.core.Ordered 接口做到這一點。 在兩個切面中,Ordered.getValue() 方法返回值較低的那個有更高的優先級。

6.2.5. 引入(Introductions)

引入(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");

6.2.6. 切面實例化模型

這是一個高級主題...

默認情況下,在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”完全一樣,只不過是爲每個匹配於連接點的獨立目標對象創建一個切面實例。

6.2.7. 例子

現在你已經看到了每個獨立的部分是如何運作的了,是時候把他們放到一起做一些有用的事情了!

因爲樂觀鎖的關係,有時候business services可能會失敗(有人甚至在一開始運行事務的時候就失敗了)。如果重新嘗試一下,很有可能就會成功。 對於business services來說,重試幾次是很正常的(Idempotent操作不需要用戶參與,否則會得出矛盾的結論) 我們可能需要透明的重試操作以避免讓客戶看見OptimisticLockingFailureException 例外被拋出。 很明顯,在一個橫切多層的情況下,這是非常有必要的,因此通過切面來實現是很理想的。

因爲我們想要重試操作,我們會需要使用到環繞通知,這樣我們就可以多次調用proceed()方法。下面是簡單的切面實現:

@Aspect
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;
}

@Around("com.xyz.myapp.SystemArchitecture.businessService()")
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 意外,我們只需要簡單的重試,直到我們耗盡所有預設的重試次數。

對應的Spring配置如下:

<aop:aspectj-autoproxy/> <bean id="optimisticOperationExecutor" class="com.xyz.myapp.service.impl.OptimisticOperationExecutor"> <property name="maxRetries" value="3"/> <property name="order" value="100"/> </bean>

爲了改進切面,使之僅僅重試idempotent操作,我們可以定義一個 Idempotent 註解:

@Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation }

並且對service操作的實現進行註解。 這樣如果你只希望改變切面使得idempotent的操作會嘗試多次,你只需要改寫切入點表達式,這樣只有 @Idempotent 操作會匹配:

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doOptimisticOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}

6.3. Schema-based AOP support

如果你無法使用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元素,並且三者必須按照這樣的順序進行聲明。

6.3.1. 聲明一個切面

有了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一樣被容器管理配置以及依賴注入。

6.3.2. 聲明一個切入點

切入點可以在切面裏面聲明,這種情況下切入點只在切面內部可見。切入點也可以直接在<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風格受到了很多的限制。

6.3.3. 聲明通知

和@AspectJ風格一樣,基於schema的風格也支持5種通知類型並且兩者具有同樣的語義。

6.3.3.1. 通知(Advice)

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"會被調用。

6.3.3.2. 返回後通知(After returning advice)

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) {...

6.3.3.3. 拋出異常後通知(After throwing advice)

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) {...

6.3.3.4. 後通知(After (finally) advice)

After (finally)通知在匹配方法退出後執行。使用 after 元素來聲明:

<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>

6.3.3.5. 通知

Around通知是最後一種通知類型。Around通知在匹配方法運行期的“周圍”執行。 它有機會在目標方法的前面和後面執行,並決定什麼時候運行,怎麼運行,甚至是否運行。 Around通知經常在需要在一個方法執行前或後共享狀態信息,並且是線程安全的情況下使用(啓動和停止一個計時器就是一個例子)。 注意選擇能滿足你需求的最簡單的通知類型(i.e.如果簡單的before通知就能做的事情絕對不要使用around通知)。

Around通知使用 aop:around 元素來聲明。 通知方法的第一個參數的類型必須是 ProceedingJoinPoint 類型。 在通知的主體中,調用 ProceedingJoinPointproceed() 方法來執行真正的方法。 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;
}

6.3.3.6. 通知參數

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屬性接受由逗號分割的參數名列表。

6.3.3.7. 通知順序

當同一個切入點(執行方法)上有多個通知需要執行時,執行順序規則在 Section 6.2.4.7, “通知(Advice)順序” 已經提及了。 切面的優先級通過切面的支持bean是否實現了Ordered接口來決定。

6.3.4. 引入

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");

6.3.5. 切面實例化模型

Schema-defined切面僅支持一種實例化模型就是singlton模型。其他的實例化模型或許在未來版本中將得到支持。

6.3.6. Advisors

"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 。

6.3.7. 例子

讓我們來看看在 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)"/>

6.4. 混合切面類型

我們完全可以混合使用以下幾種風格的切面定義:使用自動代理的@AspectJ 風格的切面,schema-defined <aop:aspect> 的切面,和用 <aop:advisor> 聲明的advisor,甚至是使用Spring 1.2風格的代理和攔截器。 由於以上幾種風格的切面定義的都使用了相同的底層機制,因此可以很好的共存。

6.5. 代理機制

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"/>

6.6. 編程方式創建@AspectJ代理

除了在配置文件中使用 <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();

6.7. 在Spring應用中使用AspectJ

到目前爲止本章討論的一直是純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)。

6.7.1. 在Spring中使用AspectJ來爲domain object進行依賴注入

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>

6.7.1.1.  @Configurable object的單元測試

提供 @Configurable 支持的一個目的就是使得domain object的單元測試可以獨立進行,不需要通過硬編碼查找各種倚賴關係。 如果 @Configurable 類型沒有通過AspectJ織入, 則在單元測試過程中註解不會起到任何作用,測試中你可以簡單的爲對象的mock或者stub屬性賦值,並且和正常情況一樣的去使用該對象。 如果@Configurable 類型通過AspectJ織入, 我們依然可以脫離容器進行單元測試,不過每次創建一個新的 @Configurable 對象時都會看到一個warning標示該對象不受Spring管理配置。

6.7.1.2. 多application context情況下的處理

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實例,這可能並不是你所想要的。

6.7.2. Spring中其他的AspectJ切面

除了 @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);
}

6.7.3. 使用Spring IoC來配置AspectJ的切面

當在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>

6.7.4. 在Spring應用中使用AspectJ Load-time weaving(LTW)

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,但是隻希望使用配製管理切面而不需要事務管理的話,你可以像下面那樣定義:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN"    "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <aspects> <include within="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/> </aspects> <weaver options="-showWeaveInfo,-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler"> <include within="com.xyz.myapp..*"/> </weaver> </aspectj>

在Java 5平臺下,LTW可以通過虛擬機的參數來啓用。

-javaagent:<path-to-ajlibs>/aspectjweaver.jar

6.8. 其它資源

更多關於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場景進行了比較深入的研究。

發佈了6 篇原創文章 · 獲贊 18 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章