程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> AOP@Work:介紹AspectJ 5 - AspectJ中的Java 5支持和其他新特性搶鮮看

AOP@Work:介紹AspectJ 5 - AspectJ中的Java 5支持和其他新特性搶鮮看

編輯:關於JAVA

AspectJ 5 (目前處在它的第二個裡程碑版本)的主要重點是對 Java 5 中引 入的新 Java 語言特性(包括注釋和泛型)提供支持。AspectJ 5 還包含沒有加 入 Java 5 的新特性,例如基於注釋的開發風格、改進的裝入時織入和新的方面 實例化模型。

在 AOP@Work 系列的這一期中,我概述了 AspectJ 5 語言和包含 AspectJ 編 譯器及相關工具的 AspectJ 5 版本。我先介紹如何用 AspectJ 5 編譯器編譯 Java 應用程序 (既可以用命令行編譯器也可以用 AspectJ 開發工具(AJDT;請 參閱 參考資料)),然後,我提供了使用 Java 5 特性實現 AspectJ 應用程序 的一些示例。我還討論了 擦除(erasure) 對 AOP 系統的意義,這是 Java 5 中用來實現泛型的技術,我還解釋了 AspectJ 解決問題的方式。這篇文章中描述 的有些特性只能用即將推出的 AspectJ 5 M3 版本編譯(計劃在 2005 年 7 月發 布)。

也可以下載以下示例中使用的 AJDT 或命令行 AspectJ 編譯器。請參閱 參考 資料 獲得技術下載的鏈接。

用 AspectJ 編譯 Java 5 應用程序

AspectJ 編譯器 (ajc)支持在版本 1.3(及以前版本)、1.4 和 5.0 的兼 容級別上編譯 Java 源代碼,並生成針對 1.1 版以上 VM 的字節碼。像 javac 一樣,ajc 有一些限制:在 1.4 版本兼容級別上編譯源代碼只支持 1.4 及以上 版本的目標 VM,在 5.0 版本兼容級別上編譯源代碼只支持 5.0 版本的目標 VM 。

AspectJ 編譯器的默認兼容級別 是使用 5.0 的源代碼級別,並生成針對 5.0 VM 的字節碼。可以傳遞 -1.5 編譯器選項,顯式地把源代碼兼容級別和目標級別 設置為針對 Java 5。假設想用 AspectJ 5 編譯器處理 Java 1.4 語言並針對 1.4 VM,那麼只需傳遞 -1.4 即可。

AspectJ 5 織入器也默認在 Java 5 兼容模式下運行。在這個模式中,織入器 會正確地解釋 Java 5 中的新特性;例如,編譯器在確定 args(Integer) 是否匹 配 int 參數時,會考慮自動裝箱和拆箱。如果不是從源文件編譯,而是用編譯後 的 Java 5 .class 文件 (在 inpath 上),使用 AspectJ 編譯器來織入方面( 在 aspectpath 上),那麼這就是想要的行為。傳遞 -1.4 或 -1.3 選項會禁用 Java 5 特性。

AspectJ Development Environment Guide 包含更多關於新的編譯器標志和選 項的信息。請參閱 參考資料 一節訪問這個指南。

用 AJDT 和 Eclipse 編譯

如果正在用 AJDT 編譯和運行 AspectJ 程序,那麼 AspectJ 就繼承了 Eclipse 中為 Java 編譯指定的編譯器選項。在這種情況下,可以對 AspectJ 進 行配置,把 Java 5 模式作為工作區選項配置使用,或者以每個項目為基礎使用 。只要進入 Java 編譯器選項配置頁,把 Compiler compliance level 屬性設置 為 5.0 即可。如果正在從 JDK 1.4 升級,那麼可能還需要在項目的 build 設置 中把 JRE 系統庫更新到 Java 5 JRE。

圖 1 顯示了 AJDT 的 Java 編譯器選項配置頁和一個用於 Java 5.0 兼容級 別的選項配置設置。

圖 1. 在 Eclipse 中指定 5.0 兼容級別

在方面中使用 Java 5 的特性

有了這些基礎知識,現在可以把注意力轉到在 AspectJ 中使用 Java 5 特性 了。我選擇了一個示例方面,它支持一組基本的生命周期操作,可以用於任何被 注釋為 ManagedComponent 的類型 。ManagedComponent 是一個簡單的標記注釋 ,如下所示:

public @interface ManagedComponent {}

方面本身被設計成可以表現許多 Java 5 和 AspectJ 5 語言的特性,包括枚 舉、注釋、新風格的 for 循環以及泛型。LifecycleManager 方面的第一部分僅 定義了 enum,表示托管組件可能存在的狀態,還定義了托管組件將會支持的 Lifecycle 接口,如清單 1 所示:

清單 1. 有 State 和 Lifecycle 聲明的 LifecycleManager 方面

/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
public aspect LifecycleManager {
  /**
   * The defined states that a managed component can be in.
   */
  public enum State {
    INITIAL,
    INITIALIZING,INITIALIZED,
     STARTING,STARTED,
    STOPPING,
     TERMINATING,TERMINATED,
    BROKEN;
  }

  /**
   * The lifecycle interface supported by managed components.
   */
  public interface Lifecycle {
    void initialize ();
    void start();
    void stop();
    void terminate();
    boolean isBroken();
    State getState ();
    void addObserver(LifecycleObserver observer);
     void removeObserver(LifecycleObserver observer);
  }

   ...

基於注釋的類型匹配

方面的下一部分使用了一些新的 AspectJ 5 支持,以進行基於注釋的類型匹 配。這說明任何具有 ManagedComponent 注釋的類型都要實現 Lifecycle 接口( 並且因此稍後在方面中將會獲得為此類組件定義的全部行為)。類型模式 “@ManagedComponent *”匹配具有 ManagedComponent 注釋、名稱任意的類型, 如清單 2 所示:

清單 2. 用基於注釋的類型匹配聲明雙親

  /**
   * Any type with an @ManagedComponent annotation implements
   * the Lifecycle interface (and acquires the default implementation
   * defined in this aspect if none is provided by the type).
   */
  declare parents : @ManagedComponent * implements Lifecycle;

LifeCycleObserver 接口

清單 3 顯示了 Lifecycle 中的添加/刪除觀察者操作中引用的 LifecycleObserver 接口的定義:

清單 3. LifecycleObserver 接口

  /**
   * Interface to be implemented by any type needing to
   * observe the lifecycle events of managed components.
    */
  public interface LifecycleObserver {
    void componentInitialized(Lifecycle component);
    void componentStarted(Lifecycle component);
    void componentStopped(Lifecycle component);
    void componentTerminated(Lifecycle component);
    void componentBroken(Lifecycle component);
  }

對於沒有提供自己的定義的所有實現者,方面提供了 Lifecycle 操作的默認 實現。它還為所有實現者聲明了私有的 state 和 observers 字段。注意 state 字段是枚舉類型,而 observers 字段使用參數化類型,如清單 4 所示:

清單 4. Lifecycle 接口的默認實現

  // default implementations for the state-based lifecycle events
  private State Lifecycle.state = State.INITIAL;
   public void Lifecycle.initialize() {}
  public void Lifecycle.start() {}
  public void Lifecycle.stop() {}
   public void Lifecycle.terminate() {}
  public boolean Lifecycle.isBroken() { return state == State.BROKEN; }
  public State Lifecycle.getState() { return state; }

  // default implementation of the add/remove observer lifecycle operations
   private List<LifecycleObserver> Lifecycle.observers = new ArrayList<LifecycleObserver>();
  public void Lifecycle.addObserver(LifecycleObserver observer) {
     observers.add(observer);
  }
  public void Lifecycle.removeObserver(LifecycleObserver observer) {
     observers.remove(observer);
  }

狀態管理和事件處理

因為我想在這篇文章中介紹許多基礎知識,所以從剩下的方面實現中我只摘錄 幾個。對於每個生命周期事件,方面都提供 before 和 after returning 建議, 以驗證托管組件處於有效狀態,從而執行操作並把變化通知給已注冊的觀察者, 如下所示:

清單 5. 狀態管理和通知

  // these pointcuts capture the lifecycle events of managed components
  pointcut initializing(Lifecycle l) : execution(* Lifecycle.initialize(..)) && this(l);
  pointcut starting (Lifecycle l) : execution(* Lifecycle.starting(..)) && this (l);
  pointcut stopping(Lifecycle l) : execution(* Lifecycle.stopping(..)) && this(l);
  pointcut terminating(Lifecycle l): execution(* Lifecycle.terminating(..)) && this(l);

  /**
   * Ensure we are in the initial state before initializing.
   */
  before(Lifecycle managedComponent) : initializing(managedComponent) {
    if (managedComponent.state != State.INITIAL)
      throw new IllegalStateException("Can only initialize from INITIAL state");
     managedComponent.state = State.INITIALIZING;
  }

   /**
   * If we successfully initialized the component, update the state and
   * notify all observers.
   */
  after (Lifecycle managedComponent) returning : initializing(managedComponent) {
    managedComponent.state = State.INITIALIZED;
    for (LifecycleObserver observer: managedComponent.observers)
       observer.componentInitialized(managedComponent);
  }

注意建議體中使用了新風格的 for 循環,對所有已注冊的觀察者進行迭代。 如果生命周期操作正常返回,就執行 after returning 建議。如果生命周期操作 通過運行時異常而退出,那麼後面的建議(清單 6)就把組件轉變成 BROKEN 狀 態。可以想像,方面中會有進一步的建議,防止對狀態是 BROKEN 的托管組件執 行任何操作,但是這個討論超出了這篇文章的范圍:

清單 6. 故障檢測和到 BROKEN 狀態的轉變

   /**
   * If any operation on a managed component fails with a runtime exception
   * then move to the broken state and notify any observers.
   */
  after(Lifecycle managedComponent) throwing(RuntimeException ex) :
    execution (* *(..))
    && this(managedComponent) {
     managedComponent.state = State.BROKEN;
    for (LifecycleObserver observer: managedComponent.observers)
       observer.componentBroken(managedComponent);
  }

示例方面已經表明,在方面中使用 Java 5 特性,就像在類中使用它們一樣簡 單。而且從匹配的角度來看,根據注釋的存在與否(在 declare parents 語句中 ),示例方面也給出了 AspectJ 5 能做什麼的提示。但是在 AspectJ 5 中除了 注釋匹配之外還有許多東西,在下一節中會看到。

連接點匹配和注釋

在 AOP@Work 系列以前的文章中,介紹了注釋、元數據和面向方面編程之間的 關系 (請參閱 參考資料 一節中的 “AOP and metadata”),所以這裡不再贅 述,直接介紹 AspectJ 5 能做的一些事情。

出於示例的原因,我將采用 EJB 3.0 規范中的一些注釋(請參閱 參考資料) 。對於具有相關事務策略的方法,可以用 @Tx 注釋進行注釋。例如:

@Tx(TxType.REQUIRED)
void credit(Money amount) {...}

如果想編寫 TransactionManager 方面,那麼可能會對帶有 @Tx 注釋的方法 的執行感興趣。編寫與它們匹配的切入點很簡單,如清單 7 所示:

清單 7. 匹配事務性方法的執行

public aspect TransactionManager {
  /**
   * The execution of any method that has the @Tx
   * annotation
   */
  pointcut transactionalMethodExecution() :
     execution(@Tx * *(..));

  /**
   * Placeholder for implementing tx policies
   */
  Object around() : transactionalMethodExecution() {
    return proceed();
  }

}

匹配注釋方法的調用和執行

execution(@Tx * *(..)) 切入點表達式匹配任何方法的執行,可以使用任何 名稱、任何類型、任何參數,只要方法用 @Tx 注釋。如果需要,也可以縮小范圍 。到事務性方法的匹配 調用 同樣簡單,只需編寫“call(@Tx * *(..))”即可。

在這種情況下,實現事務策略的建議需要知道執行方法上的 @Tx 注釋的 value。使用 AspectJ,可以把連接點的上下文值綁定到切入點表達式,然後向建 議公布上下文。在 AspectJ 5 中,用新的切入點指示符 @annotation 把這個能 力擴展到了注釋上。像 AspectJ 中所有的其他上下文綁定切入點指示符一樣, @annotation 扮演著雙重角色:既把連接點匹配限制到主題(方法、字段、構造 函數等)具有指定類型注釋的連接點上,又公開那個值。可以很容易地重新定義 TransactionManager 方面,讓它利用這一優點,如下所示:

清單 8. 公開注釋值

public aspect TransactionManager {
  /**
   * The execution of any method that has the @Tx
   * annotation
   */
  pointcut transactionalMethodExecution(Tx tx) :
     execution(* *(..)) && @annotation(tx);

  /**
   * Placeholder for implementing tx policies
   */
  Object around(Tx tx) : transactionalMethodExecution(tx) {
    TxType transactionType = tx.value();
    // do before processing
     Object ret = proceed(tx);
    // do after processing
     return ret;
  }

}

運行時持久性

在使用 @annotation 來匹配注釋時,注釋類型必須擁有運行時持久性(否則 AspectJ 就不能在運行時公開注釋值)。就像前面看到的,匹配只使用 execution 就能處理只具有類文件持久性的注釋。

到目前為止所展示的技術,處理的都是基於字段的連接點。假設有個字段用 @ClassifiedData 注釋,那麼可以編寫清單 9 所示的兩個切入點中的一個,具體 取決於是否需要公開實際的注釋值:

清單 9. 帶注釋的字段

 /**
  * Any access or update to classified data
  */
  pointcut classifiedAction() :
   get(@ClassifiedData * *) || set(@ClassifiedData * *);

 /**
  * Alternative declaration:
  * Any access or update to classified data,
  * exposes the annotation to provide access to
  * classification level attribute
  */
  pointcut classifiedAction(ClassifiedData classification) :
   (get(* *) || set(* *)) && @annotation(classification);

匹配帶注釋的類型

在結束關於注釋的討論之前,先深入研究一下 AspectJ 5 如何支持在類型上 進行注釋匹配的。AspectJ 允許指定類型模式,可以用注釋模式 限定該模式。到 目前為止,我一直用的都是最簡單的注釋模式 @Foo,它在主題有 Foo 注釋時匹 配。可以進行組合,在主題既有 Foo 注釋 又 有 Goo 注釋時,“@Foo @Goo”匹 配。在主題或者有 Foo 注釋或者 有 Goo 注釋時,“@(Foo || Goo)”匹配。請 參閱 AspectJ 5 Developers Guide 中關於注釋模式的討論(在 參考資料 中) ,獲取更多細節。

在 EJB 3.0 中,會話 bean 可以使用 @Stateful 或 @Stateless 進行注釋。 類型模式“@(Stateless || Stateful) *”匹配的是有這兩個注釋之一的類型。 出於某種原因,如果想把 TransactionManager 方面限制到只處理會話 bean,那 麼可以像下面這樣重新定義 清單 8 的 transactionalMethodExecution 切入點 。

  pointcut transactionalMethodExecution(Tx tx) :
     execution(* *(..)) && @annotation(tx)
    && within(@(Stateless || Stateful) *);

可以把這段代碼讀作“匹配在具有 Stateless 或 Stateful 注釋的類型中帶 有 Tx 注釋的任何方法的執行”。另一種編寫它的方法是直接在切入點表達式中 表達這個類型模式:execution(* (@(Stateless || Stateful) *).*(..)),但是 我認為前者更清楚。(注意,如果使用 call 則不是 execution,那麼兩者之間 會有顯著差異:前者匹配從會話 bean 中發出的對事務方法的調用,而後者匹配 對在會話 bean 中定義的事務方法的調用。)

更多切入點指示符

AspectJ 為匹配和公開注釋定義了更多切入點指示符:

@withincode 匹配的連接點,由擁有指定注釋的成員(方法、構造函數、建議 )代碼的執行產生。 @within 匹配的連接點在擁有指定注釋的類型內部。 @this 在匹配的連接點中,對象目前綁定到有指定注釋的 this。 @target 匹配的連接 點的目標有指定注釋。 @args 匹配的連接點的參數擁有指定注釋。

AspectJ 5 中的泛型

Java 語言中對泛型的新支持是 Java 5 中引入的爭議最大的變化。泛型 聲明 時使用一個或多個 類型參數,而這些類型參數在聲明該類型的變量時綁定到具體 的類型規范。泛型最常使用的示例是 Java 的集合類。Java 5 中的 List 接口是 個泛型,帶有一個類型參數 —— 列表中元素的類型。根據約定,單一字母用於 表示類型參數,list 接口可能聲明為:public interface List<E> {...} 。如果想創建引用字符串列表的變量,可以把它聲明為類型 List<String> 。泛型 List<E> 把自己的類型參數 E 綁定到 String,從而創建 參數化 類型 List<String>。

從 清單 4 顯示的代碼中摘出來的 LifecycleManager 方面,包含類型間聲明 中使用的參數化類型 (List<LifecycleObserver>)的一個示例。AspectJ 5 也允許在泛型上進行類型間聲明。清單 10 顯示了一個泛型 DataBucket<T> ,以及一個在它上面進行類型間聲明的方面:

清單 10. 泛型上的類型間聲明

 public class DataBucket<T> {

  private T data;

  public T getData() {
   return data;
  }

  public void setData(T data) {
   this.data = data;
  }

 }
 aspect DataHistory {

  private E DataBucket<E>.previousDataValue;

  private E DataBucket<E>.getPreviousDateValue() {
   return previousDataValue;
  }

 }

注意,類型間聲明中使用的類型參數名稱不必與 DataBucket 類本身聲明中使 用的類型參數名稱對應;相反,類型的 簽名 必須匹配(類型參數的數量,以及 通過 extends 或 super 子句放在類型參數上的限制)。

這一節的其余部分,我把重點放在切入點表達式中通用簽名和類型的匹配上。 在隨後的討論中,把切入點指示符分成兩種類型是有幫助的:一類是基於靜態簽 名進行匹配的切入點指示符(execution、call、get、 set ,等等),另一類是 根據運行時類型信息進行匹配的切入點指示符(this、target、args)。由於存 在叫做 擦除(erasure) 的東西(我馬上就會介紹),所以這個區分很重要。

匹配通用簽名和類型

對於基於簽名進行匹配的切入點指示符,AspectJ 采取了一種簡單方式:類型 的類型參數規范就是簽名的一部分。 例如,以下方法都是不同的簽名:

void process(List<Number> numbers)

void process(List<? extends Number> numbers)

void process(List<?> items)

這些方法的執行可以用以下切入點分別匹配:

execution(* process(List<Number>))

execution(* process(List<? extends Number>))

execution(* process(List<?>))

AspectJ 在匹配類型的時候,支持 * 和 + 通配符。表達式“execution(* process(List<*>))”匹配全部三個 process 方法,因為 * 匹配任何類型 。但是,表達式“execution(* process(List<Number+>))“只匹配第一個 process 方法(Number 由模式 Number+ 匹配),但是 不匹配 第二個或第三個 。可以把模式 List<Number+> 擴展到與 List<Float>、 List<Double>、List<Integer> 等匹配,但是對於 List<? extends Number> 來說,這些都是不同的簽名。有一個重要的區別是,請考慮 這樣一個事實:在 process 方法的方法體內,用沒有通配的簽名插入列表是合法 的,但是在使用 ? extends 格式的時候就不合法了。

需要記住的規則是:泛型通配符是簽名的組成部分,而且 AspectJ 模式通配 符被用來 匹配 簽名。

基於運行時類型信息的匹配

在根據運行時類型信息進行匹配時,事情變得更有趣了。this、target 和 args 切入點指示符全都根據運行時類型信息進行匹配。請考慮 process 方法的 另一個變體:

void process(Number n) {...}

可以靜態地決定切入點表達式“execution(* process(..)) &&args (Number)”以總是 匹配這個方法的執行 —— 傳遞的參數保證是數字。相反,如 果編寫的是“execution(* process(..)) &&args(Double)”,那麼這個 表達式可能 匹配這個方法的執行,具體取決於實際運行時傳遞的參數的類型。在 這種情況下,AspectJ 應用運行時測試來判斷參數是不是 instanceof Double。

現在再考慮一下采用參數化類型的 process 方法的以下簽名:

void process(List<? extends Number> ns) {...}

然後應用相同的推斷,就可以看出:

execution(* process(..)) &&args(List<? extends Number>) 總是會匹配,因為不論傳遞什麼類型的列表,都必須滿足這個規范 。 execution(* process(..)) && args(List<String>) 永遠不 會匹配,因為字符串列表永遠不會傳遞給這樣的方法,該方法期待得到擴展 Number 的東西的列表。 execution(* process(..)) && args (List<Number>) 可能 匹配,具體取決於實際傳遞的列表是數字列表、雙 精度列表,還是浮點列表。

在後一種情況下,可能 做的工作就是在實際的參數上應用運行時測試,判斷 它是不是 instanceof List<Number>。不幸的是,Java 5 實現泛型時采用 了一種叫做 擦除(erasure) 的技術 —— 被擦除的就是參數化類型的運行時類 型參數信息。在運行時,參數只被當作普通的 List(所謂參數的“原始”類型) 。

即使缺少必要的信息進行確定的決策,AspectJ 也必須決定這類切入點是否應 當匹配。在 Java 語言中對於這類情況有一種優先級:在把原始類型(例如 List )的實例傳遞給需要參數化類型(例如 List<Number>)的方法時,調用會 通過 Java 5 編譯器傳遞,但是會生成一個“unchecked”警告,提示轉換可能不 是類型安全的。

類似地,當 AspectJ 認為某個切入點可能匹配指定連接點,但是不能應用運 行時測試進行確定的時候,就會考慮對切入點進行匹配,而且 AspectJ 編譯器會 提出一個“unchecked”警告,表示實際的匹配不能被檢測。就像 Java 語言支持 @SuppressWarnings 注釋,可以在成員中抑制未檢測警告一樣,AspectJ 支持 @SuppressAjWarnings 注釋,可以用它對建議進行注釋,以抑制從切入點匹配發 生的未檢測警告。

執行連接點和泛型

在離開泛型主題之前,有一個要點需要考慮。回到 清單 10 中定義的 DataBucket 類,請注意不論用多少不同的類型參數去實例化 DataBucket 的實例 (如下所示),都只有一個 DataBucket 類:

DataBucket<Integer> myIntBucket = new DataBucket<Integer>();
DataBucket<String> myStringBucket = new DataBucket<String>();
DataBucket<Food> myFoodBucket = new DataBucket<Food>();
...

它的含義就是,在 DataBucket 類的內部,沒有返回 String、Integer 或 Food 實例的 getData 方法執行這樣的東西。相反,只有一個 getData 方法執行 ,返回類型參數 T 的實例。所以可以這樣編寫,它匹配的方法執行,是在命名類 型 DataBucket 中返回 T 的 getData 方法,其中 T 是類型參數:

execution<T>(T DataBucket<T>.getData())

@AspectJ 注釋

介紹了 AspectJ 5 的新發行版中最重要的 Java 5 語言特性之後,我現在把 重點轉到一些沒有顯式地捆綁到 Java 5 的新特性上。其中最重要的一個是方面 聲明的新的基於注釋的風格,稱作 @AspectJ 注釋。在 AspectJ 5 中,可以用普 通的 Java 語法編寫方面,然後對聲明進行注釋,這樣,它們就可以由 AspectJ 的織入器解釋。例如,在代碼風格中,可以這樣編寫:

public aspect TransactionManager {
 ...

在基於注釋的風格中,可以這樣編寫:

@Aspect
public class TransactionManager {
 ...

隨著 AspectJ 與 AspectWerkz 在 2005 年初的合並,@AspectJ 注釋被添加 到 AspectJ 中。它們使得任何標准的 Java 5 編譯器都可以處理 AspectJ 源代 碼,而實際上任何從 Java 源代碼起工作的工具都可以。在使用沒有為操作 AspectJ 程序提供集成支持的 IDE 時(當然,這類環境也缺乏顯示橫切結構的視 圖),它們也為日常的編輯體驗造成顯著區別。

需要注意的重點是,AspectJ 5 發行版具有以下內容(雖然有兩種開發風格) :

一個 語言

一個 語義

一個 織入器

不論選擇用什麼風格表達方面,它們實際表達的都是同樣的東西,而且也用同 樣的方式發揮作用。這一重要屬性使得可以容易地混合和匹配風格(所以用 @AspectJ 風格開發的方面可以與用代碼風格開發的方面結合使用,反之亦然)。 但是 @AspectJ 風格有些限制。例如,在使用常規的 Java 編譯器時,就不支持 注釋風格版本的 AspectJ 構造(例如 declare soft),因為這類構造需要編譯 時支持而不是織入時支持。

現在來看一個使用 @AspectJ 注釋的示例。

用 @AspectJ 注釋編寫方面

我先從清單 11 顯示的簡化的 LifecycleManager 方面開始,並用 @AspectJ 風格重寫它:

清單 11. 簡化的生命周期管理器方面,代碼風格

/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
public aspect LifecycleManager {
/**
* The defined states that a managed component can be in.
*/
public enum State {
INITIAL,
INITIALIZING,INITIALIZED,
STARTING,STARTED,
STOPPING,
TERMINATING,TERMINATED,
BROKEN;
}
/**
* The lifecycle interface supported by managed components.
*/
public interface Lifecycle {
void initialize();
void start();
void stop();
void terminate();
boolean isBroken();
State getState();
}
/**
* Any type with an @ManagedComponent annotation implements
* the Lifecycle interface (and acquires the default implementation
* defined in this aspect if none is provided by the type).
*/
declare parents : @ManagedComponent * implements Lifecycle;
// default implementations for the state-based lifecycle events
private State Lifecycle.state = State.INITIAL;
public void Lifecycle.initialize() {}
public void Lifecycle.start() {}
public void Lifecycle.stop() {}
public void Lifecycle.terminate() {}
public boolean Lifecycle.isBroken() { return state == State.BROKEN; }
public State Lifecycle.getState () { return state; }
// these pointcuts capture the lifecycle events of managed components
pointcut initializing (Lifecycle l) : execution(* Lifecycle.initialize(..)) && this (l);
pointcut starting(Lifecycle l) : execution(* Lifecycle.starting(..)) && this(l);
pointcut stopping (Lifecycle l) : execution(* Lifecycle.stopping(..)) && this (l);
pointcut terminating(Lifecycle l): execution(* Lifecycle.terminating(..)) && this(l);
/**
* Ensure we are in the initial state before initializing.
*/
before(Lifecycle managedComponent) : initializing(managedComponent) {
if (managedComponent.state != State.INITIAL)
throw new IllegalStateException("Can only initialize from INITIAL state");
managedComponent.state = State.INITIALIZING;
}
/**
* If we successfully initialized the component, update the state and
* notify all observers.
*/
after(Lifecycle managedComponent) returning : initializing(managedComponent) {
managedComponent.state = State.INITIALIZED;
}
...
}

方面聲明和內部的類型聲明可以容易地轉移到新風格,如清單 12 所示:

清單 12. 簡化的生命周期管理器方面,注釋風格

/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
@Aspect
public class LifecycleManager {
/**
* The defined states that a managed component can be in.
*/
public enum State {
INITIAL,
INITIALIZING,INITIALIZED,
STARTING,STARTED,
STOPPING,
TERMINATING,TERMINATED,
BROKEN;
}
/**
* The lifecycle interface supported by managed components.
*/
public interface Lifecycle {
void initialize();
void start();
void stop();
void terminate();
boolean isBroken();
State getState();
}
...

與切入點和建議一起使用注釋

下面,我們來看看用 @AspectJ 風格重寫切入點和建議時發生了什麼。切入點 是在與切入點具有相同簽名的 void方法上使用 @Pointcut注釋而編寫的。建議則 是在方法上使用 @Before、 @Around、@AfterReturning、@AfterThrowing和 @After注釋而編寫的,如清單 13 所示:

清單 13. 注釋風格的切入點和建議

/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
@Aspect
public class LifecycleManager {
...
// these pointcuts capture the lifecycle events of managed components
@Pointcut(
"execution(* org.aspectprogrammer.devWorks.LifecycleManager
.Lifecycle.initialize(..))
&& this(l)"
)
void initializing(Lifecycle l) {}
@Pointcut(
"execution(* org.aspectprogrammer.devWorks.LifecycleManager
.Lifecycle.starting(..))
&& this(l)"
)
void starting(Lifecycle l){}
@Pointcut(
"execution(* org.aspectprogrammer.devWorks.LifecycleManager
.Lifecycle.stopping(..))
&& this(l)"
)
void stopping(Lifecycle l) {}
@Pointcut(
"execution(* org.aspectprogrammer.devWorks.LifecycleManager
.Lifecycle.terminating(..))
&& this(l) "
)
void terminating(Lifecycle l) {}
/**
* Ensure we are in the initial state before initializing.
*/
@Before("initializing(managedComponent)")
public void moveToInitializingState(Lifecycle managedComponent) {
if (managedComponent.state != State.INITIAL)
throw new IllegalStateException(
"Can only initialize from INITIAL state");
managedComponent.state = State.INITIALIZING;
}
/**
* If we successfully initialized the component, update the state and
* notify all observers.
*/
@AfterReturning("initializing (managedComponent)")
public void moveToInitializedStated (Lifecycle managedComponent) {
managedComponent.state = State.INITIALIZED;
}

注意,在切入點表達式中,任何引用的類型都必須是全限定的(導入語句只能 在源代碼條件下存在,在處理注釋時,對織入器不可用)。建議方法必須聲明成 public,並返回 void (@Around建議除外,它必須返回值)。

與類型間聲明一起使用注釋

現在剩下的就是把類型間聲明也轉移到 @AspectJ 風格的方面了。在注釋風格 中,這些聲明像清單 14 所示這樣進行:

清單 14. 注釋風格的類型間聲明

/**
* This aspect provides default lifecycle management for all
* types with the @ManagedComponent annotation.
*/
@Aspect
public class LifecycleManager {
...
// default implementations for the state-based lifecycle events
@DeclareParents("@ManagedComponent *")
class DefaultLifecycleImpl implements Lifecycle {
private State state = State.INITIAL;
public void initialize() {}
public void start() {}
public void stop() {}
public void terminate() {}
public boolean isBroken() { return state == State.BROKEN; }
public State getState() { return state; }
}
...
}

關於注釋風格的開發,還有許多其他有趣的問題,例如如何在注釋風格的建議 方法的方法體內引用 thisJoinPoint,proceed在 around建議中是如何被支持的 。要獲取這些主題的更多信息,請參閱 AspectJ 5 Developers Guide。

裝入時織入的增強

裝入時織入指的是在類裝入 VM 時織入類的過程(比照提前織入而言 —— 例 如編譯時織入)。從 1.1 發行版起,AspectJ 就擁有支持裝入時織入必需的基礎 設施,但是必須編寫定制的類裝入器,才能真正把 AspectJ 的織入器集成進應用 程序。在 AspectJ 1.2 發行版中,隨著添加了 aj腳本,裝入時織入得到改進, aj 能夠從命令行裝入和運行任何 Java 應用程序,也可以在類裝入時從 ASPECTPATH織入方面。這個腳本支持 JDK 1.4 以上版本。

但是,命令行腳本不能方便地用在所有環境,特別是不能很好地與 Java EE 應用程序集成。在 AspectJ 5 中,通過放在類路徑中的 META-INF/aop.xm, AspectJ 支持對裝入時織入進行配置。這是隨著 2005 年初與 AspectWerkz 的合 並而帶給 AspectJ 的另一個特性。

現在來看看 aop.xml 文件和它的相關元素。

裝入時織入的 XML 規范

aop.xml 文件包含兩個主要小節:aspects元素定義用於裝入時織入的方面集 合,weaver元素指定控制織入器行為的選項(主要是控制應當織入哪個類型)。 清單 15 顯示了一個示例文件:

清單 15. 示例 aop.xml 文件

<aspectj>
<aspects>
<!-- declare two existing aspects to the weaver -->
<aspect name="com.MyAspect"/>
<aspect name="com.MyAspect.Inner"/>
<!-- define a concrete aspect inline -->
<concrete-aspect name="com.xyz.tracing.MyTracing"
extends="tracing.AbstractTracing">
<pointcut name="tracingScope" expression="within (com.xyz..*)"/>
</concrete-aspect>
</aspects>
<weaver options="-XlazyTjp">
<include within="com.xyz..*"/>
</weaver>
</aspectj>

在 aspects元素中,或者通過名稱,或者在 aop.xml 文件內部定義,把已知 的方面定義到織入器。後一種技術只能用於擴展現有抽象方面(有一個或多個抽 象切入點):切入點表達式在 XML 中提供。對於“基礎設施”方面,這可以是把 配置(切入點表達式)外部化的很好方法。定義了織入器中的方面集合之後,如 果需要(上面代碼中沒顯示),可以使用一個或多個可選的 include和 exclude 元素,控制在織入過程中實際使用哪些方面。默認情況下,織入器使用所有定義 的方面。

weaver元素包含傳遞給織入器的選項,和應當被織入(通過 include語句)的 類型集合的一個可選定義。如果沒有指定 include語句,那麼所有類型都可供織 入器進行織入。

如果在類路徑中有多個 META-INF/aop.xml 文件,那麼它們的內容就聚合在一 起,形成傳遞給織入器的完整規范。

AspectJ 5 支持許多 agents,可以把裝入時能力集成到現有環境。清單 16 顯示了使用 JVMTI (Java 5) 代理時的示例 JVM 啟動選項,任何符合 Java 5 規范的 JVM 都支持它:

清單 16. JVMTI 代理

-javaagent=aspectjweaver.jar

AspectJ 5 還自 帶了 JRockit 代理,它支持的功能與 Java 5 之前的 JRockit VM 一樣 (JRockit 還支持 Java 5 上的 jvmti)。等價的啟動選項是 - Xmanagement:class=org.aspectj.weaver.tools. JRockitWeavingAgent。

在 AspectJ 5 Developers Guide中,可以發現關於利用 AspectJ 5 進行裝入 時織入的更多細節。

結束語

總之,AspectJ 5 代表 AspectJ 前進的一大步。這篇文章的重點主要是新的 發行版既支持對 Java 5 語言構建的完全編譯,還支持基於注釋和泛型的連接點 匹配。

AspectJ 5 發行版中最令人興奮的兩個特性 —— 新的基於注釋的開發風格和 對 AspectJ 裝入時織入支持的增強 —— 是 AspectJ 與 AspectWerkz 合並的結 果。因為合並的重要性和特性的相關性,我在這裡對它們都進行了深入討論。

當然,一篇文章不可能覆蓋 AspectJ 5 這樣全面的發行版的全部增強。例如 ,我重點介紹了 AspectJ 中對連接點匹配的主要更新,而把相對次要的(例如處 理自動裝箱和協變返回類型的新方式)留給您自己去發現。其他沒有在這裡介紹 ,但是值得研究的特性包括:新的 pertypewithin方面實例化模型、在運行時詢 問方面類型的反射 API、對 declare soft處理運行時異常的方式的修訂、兼容性 、織入性能的提高,等等。

讀到這裡,我可以肯定,您可以猜測得到從哪繼續學習這些(及更多)新特性 。您猜對了,就是 AspectJ 5 Developers Guide。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved