AspectJ 編程指南

寫在前面的話

最近遇到一個需求:用戶快速點擊某個按鈕時,或者快速點某幾個按鈕時,只能響應第一個。一開始我是拒絕的,後來覺得這很合理。這功能剛好用AOP的編程思想來實現,說到AOP想到的自然是AspectJ。於是用有道翻譯,翻譯來一下官方文檔,本文只翻譯來第一部分:Getting Started with AspectJ,有什麼不對的多多指教。

AspectJ 編程指南

AspectJ 團隊
Copyright © 1998-2001 Xerox Corporation, 2002-2003 Palo Alto Research Center, Incorporated. All rights reserved.

摘要:
本編程指南描述了Aspect語言,一個附帶的指南描述了屬於AspectJ開發環境一部分的工具。如果您第一次接觸AspectJ,您應該先閱讀《Getting Started with AspectJ》,對AspectJ編程有一個廣泛的認識。如果您已經熟悉AspectJ,想深入理解,您應該閱讀《The AspectJ Language》並查看響應章節中的示例。如果想要更正式的AspectJ定義,應該閱讀《Semantics》

前言

本指南做三件事:

  1. 介紹AspectJ語言;
  2. 定義AspectJ的每個構造及其語義;
  3. 提供它們的使用示例。

它包括一些附錄,這些附錄提供了對AspectJ語法的參考、對AspectJ語義的更正式描述,以及關於AspectJ實現的說明。

第一部分,《Getting Started with AspectJ》提供了編寫AspectJ程序的簡單概述。它還展示瞭如何在現有的開發工作中分階段引入AspectJ,從而減少相關的風險。如果這是您第一次接觸AspectJ,並且您想了解AspectJ是關於什麼的,那麼您應該閱讀這一節。

第二部分,《The AspectJ Language》使用代碼片段作爲示例,更詳細地介紹該語言的特性。本文涵蓋了該語言的所有基礎知識,在閱讀本節之後,您應該能夠正確地使用該語言。

下一個部分,《Examples》包含一組完整的程序,這些程序不僅顯示所使用的特性,而且還試圖說明所推薦的實踐。在您熟悉AspectJ的元素之後,您應該閱讀這一節。

最後,有兩個章節,一章講《術語(Idioms)》,另一章講《陷阱(Pitfalls)》

後面的內容包含幾個附錄,其中包括《AspectJ Quick Reference 》對其語義的更深入的介紹,以及對其《實現註釋所》享有的緯度的描述。

1. Getting Started with AspectJ

概述

許多軟件開發人員被面向方面編程(AOP)的思想所吸引,但不確定如何開始使用這種技術。他們認識到橫切關注點的概念,並且知道他們過去在實現這些關注點時遇到了問題。但是關於如何在開發過程中採用AOP還有很多問題。常見的問題包括:

  • 我可以在現有代碼中使用aspects嗎?
  • 我可以期望從使用aspects獲得哪些好處?
  • 我如何在我的程序中找到aspects?
  • AOP的學習曲線有多陡?
  • 使用這項新技術的風險是什麼?

本章將在AspectJ: Java面向方面的通用擴展的上下文中討論這些問題。一系列經過刪節的例子說明了程序員可能希望使用AspectJ實現的方面的種類以及這樣做的好處。想要更詳細地理解示例,或者想要學習如何編寫這樣的示例的讀者,可以從AspectJ網站(http://eclipse.org/aspectj)找到更完整的示例和支持材料鏈接。

採用任何新技術的一個重大風險都是走得太遠太快。對這種風險的關注導致許多組織對採用新技術持保守態度。爲了解決這個問題,本章中的示例被分爲三個大類,其中一些方面更容易被本章前面的現有開發項目採用。下一節介紹AspectJ,我們將介紹AspectJ的核心特性,在開發方面,我們將介紹一些方面,這些方面有助於完成調試、測試和應用程序的性能調優等任務。在下面的“生產方面”一節中,我們將介紹實現Java應用程序中常見的橫切功能的方面。我們將推遲討論第三類方面,可重用方面,直到AspectJ語言。

這些類別是非正式的,並且這種順序並不是採用AspectJ的唯一方法。有些開發人員可能希望立即使用生產方面。但是我們對當前AspectJ用戶的經驗表明,這種順序允許開發人員快速獲得AOP技術的經驗(並從中受益),同時最小化風險。

AspectJ概論

本節將簡要介紹本章後面使用的AspectJ的特性。這些特性是語言的核心,但這絕不是AspectJ的完整概述。

使用一個簡單的圖形編輯器系統展示了這些特性。一個Figure由許多FigureElements組成,這些FigureElements可以是Point(點),也可以是Line(線)。Figure類提供工廠服務。還有一個Display(顯示器)。本章後面的大多數示例程序也是基於這個系統的。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mJ7graQK-1589899660860)(https://www.eclipse.org/aspectj/doc/released/progguide/figureUML.gif)]

AspectJ(以及面向方面編程)的動機是認識到存在傳統編程方法不能很好地捕獲的問題或關注點。考慮在某些應用程序中執行安全策略的問題。從本質上講,安全性跨越了應用程序模塊化的許多自然單元。此外,隨着應用程序的發展,安全策略必須一致地應用於任何添加。而且正在應用的安全策略本身可能會發生變化。在傳統編程語言中,以一種有紀律的方式捕獲關注點(比如安全策略)是困難的,而且容易出錯。

像安全性這樣的問題跨越了模塊性的自然單元。對於面向對象的編程語言,模塊化的自然單位是類。但在面向對象的編程語言中,橫切關注點不容易變成類正是因爲他們跨越類,所以這些不是可重用,他們不能精製或繼承,他們是通過程序在一個沒有紀律的方式傳播,簡而言之,他們很難處理。

AspectJ只向Java添加了一個新概念,一個連接點(join point),這實際上只是一個現有Java概念的名稱。它只向Java添加了一些新結構:切入點(pointcuts)、通知(advice)、類型間聲明(inter-type declarations)和方面(aspects)。切入點和通知動態地影響程序流,類型間聲明靜態地影響程序的類層次結構,而方面封裝了這些新構造。

連接點(join point)是程序流中定義良好的點。切入點(pointcut)在這些點上選擇特定的連接點和值。一條通知(advice )是到達連接點時執行的代碼。這些是AspectJ的動態部分。

AspectJ還有不同種類的類型間聲明,它們允許程序員修改程序的靜態結構,即類的成員和類之間的關係。

AspectJ的aspect是橫切關注點的模塊化單元。它們的行爲有點像Java類,但也可能包括切入點(pointcuts)、通知(advice )和類型間聲明(inter-type declarations)。

在接下來的小節中,我們將首先研究連接點以及它們如何組成切入點。然後我們將查看通知,這是到達切入點時運行的代碼。我們將看到如何將切入點和通知組合成方面,即AspectJ的可重用、可繼承的模塊單元。最後,我們將研究如何使用類型間聲明來處理程序類結構的橫切關注點。

動態連接點模型(The Dynamic Join Point Model)

在任何面向方面語言的設計中,連接點模型都是一個關鍵元素。連接點模型提供了公共的參考框架,這使得定義橫切關注點的動態結構成爲可能。本章描述AspectJ的動態連接點,其中連接點是程序執行過程中定義良好的特定點。
AspectJ提供了許多種類的連接點,但是本章只討論其中一種:方法調用連接點。方法調用連接點包含接收方法調用的對象的操作。它包括組成方法調用的所有操作,在所有參數被計算到幷包括return(正常情況下或通過拋出異常)之後啓動。
在運行時,每個方法調用都是一個不同的連接點,即使它來自程序中的同一個調用表達式。在執行方法調用連接點時,可能會運行許多其他連接點——在執行方法體時發生的所有連接點,以及在從方法體調用的那些方法中發生的連接點。我們說這些連接點在原始調用連接點的動態上下文中執行。

切入點(Pointcuts)

在AspectJ中,切入點選擇程序流中的某些連接點。例如,切入點

call(void Point.setX(int))

選擇每個連接點,該連接點是對具有簽名void point.setX(int)的方法的調用,即point的void setX方法帶有單個int參數。
切入點可以由其他帶有and, or, and not(拼寫&&,||,and !)的切入點構建而成。例如:

call(void Point.setX(int)) ||
call(void Point.setY(int))

選擇每個連接點,要麼是對setX的調用,要麼是對setY的調用。

切入點可以識別來自許多不同類型的連接點——換句話說,它們可以橫切類型。例如:

call(void FigureElement.setXY(int,int)) ||
call(void Point.setX(int))              ||
call(void Point.setY(int))              ||
call(void Line.setP1(Point))            ||
call(void Line.setP2(Point));

選擇每個連接點,該連接點是對五個方法之一的調用(順便說一下,第一個是接口方法)。

在我們的示例系統中,當一個FigureElement移動時,這個切入點捕獲所有連接點。雖然這是指定橫切關注點的一種有用的方法,但是它有點冗長。所以AspectJ允許程序員用切入點形式定義他們自己命名的切入點。因此,下面的代碼聲明瞭一個名爲pointcut的新點:

pointcut move():
    call(void FigureElement.setXY(int,int)) ||
    call(void Point.setX(int))              ||
    call(void Point.setY(int))              ||
    call(void Line.setP1(Point))            ||
    call(void Line.setP2(Point));

只要這個定義是可見的,程序員就可以簡單地使用move()來捕獲這個複雜的切入點。

前面的切入點都基於一組方法簽名的顯式枚舉。我們有時稱之爲基於名稱的橫切。AspectJ還提供了一些機制,允許根據方法的屬性而不是它們的確切名稱來指定切入點。我們稱之爲基於屬性的橫切。最簡單的方法涉及在方法簽名的某些字段中使用通配符。例如,切入點:

call(void Figure.make*(..))

選擇每個連接點,該連接點是對在Figure上定義的void方法的調用,該方法的名稱以“make”開頭,而不管該方法的參數是什麼。在我們的系統中,它選擇對工廠方法makePoint和makeLine的調用。切入點:

call(public * Figure.* (..))

選擇每個對Figure的公共方法的調用。

但是通配符並不是AspectJ支持的唯一屬性。另一個切入點cflow根據連接點是否出現在其他連接點的動態上下文中來識別連接點。所以,

cflow(move())

選擇發生在由move()選擇的連接點的動態上下文中的每個連接點,上面我們定義了的命名切入點。因此,它會挑選出在調用move方法和返回方法之間發生的每個連接點(正常情況下或通過拋出異常)。

通知(Advice)

所以切入點(pointcut)選擇連接點(join point)。但是它們除了選擇連接點之外什麼都不做。爲了實際實現橫切行爲,我們使用通知(advice)。通知將切入點(用於挑選連接點)和代碼體(用於在每個連接點上運行)結合在一起。
AspectJ有幾種不同的通知(advice)。Before advice 在程序處理連接點之前,到達連接點時運行。例如,在對方法調用連接點的通知開始運行之前,在實際方法開始運行之前,也就是在對方法調用的參數求值之後。

before(): move() {
    System.out.println("about to move");
}

After advice 在程序處理該連接點之後,在特定連接點上運行。例如,after advice 在方法調用中,連接點在方法體運行之後運行,就在控制權返回給調用者之前。因爲Java程序可以“正常”地離開連接點或拋出異常,所以有三種 after 通知:after returning、after throwing 和 plain after(它在返回或拋出後運行,就像Java的finally一樣)。

after() returning: move() {
    System.out.println("just successfully moved");
}

Around Advice 當到達連接點時,在連接點上運行,並對程序是否繼續使用連接點進行顯式控制。本節不討論有關建議。

在切入點中公開上下文(Exposing Context in Pointcuts)

切入點不僅可以選擇連接點,還可以在連接點上公開部分執行上下文。切入點公開的值可以在通知聲明體中使用。

一個通知聲明有一個參數列表(類似於一個方法),爲它所使用的上下文的所有部分提供名稱。例如,after advice:

after(FigureElement fe, int x, int y) returning:
        ...SomePointcut... {
    ...SomeBody...
}

使用三個公開的上下文、一個名爲fe的FigureElement和兩個名爲x和y的int。

通知的主體使用名稱就像方法參數一樣:

after(FigureElement fe, int x, int y) returning:
        ...SomePointcut... {
    System.out.println(fe + " moved to (" + x + ", " + y + ")");
}

通知的切入點發布通知的參數的值。三個基本切入點this、target和arg用於發佈這些值。所以現在我們可以寫下完整的一條通知(advice) :

after(FigureElement fe, int x, int y) returning:
        call(void FigureElement.setXY(int, int))
        && target(fe)
        && args(x, y) {
    System.out.println(fe + " moved to (" + x + ", " + y + ")");
}

從調用setXY切入點公開三個值:目標FigureElement——它發佈fe,所以它變成了after advice d 第一個參數,兩個int參數,它發佈x和y,所以他們成爲 after advice 的第二個和第三個參數。
因此,在每次setXY方法調用之後,通知將輸出被移動的figure元素及其新的x和y座標。
一個命名的切入點可能有一些參數,比如一條通知(advice)。當使用命名的切入點(通過通知,或者在另一個命名的切入點中)時,它通過名稱發佈它的上下文,就像this、target和args切入點一樣。所以另一種寫上述通知(advice)的方法是

pointcut setXY(FigureElement fe, int x, int y):
    call(void FigureElement.setXY(int, int))
    && target(fe)
    && args(x, y);

after(FigureElement fe, int x, int y) returning: setXY(fe, x, y) {
    System.out.println(fe + " moved to (" + x + ", " + y + ").");
}

類型間聲明(Inter-type declarations)

AspectJ中的類型間聲明是跨越類及其層次結構的聲明。他們可以聲明跨多個類的成員,或更改類之間的繼承關係。不同於主要動態運行的通知,引入在編譯時靜態地運行。

考慮表達由已經屬於類層次結構的某些現有類共享的功能的問題,即它們已經擴展了一個類。在Java中,創建一個接口以捕獲此新功能,然後將一個實現此接口的方法添加到 每個受影響的類中。

AspectJ可以通過使用類型間聲明在一個地方表達關注。該方面聲明實現新功能所必需的方法和字段,並將這些方法和字段與現有類相關聯。

假設我們要讓Screen對象觀察對Point對象的更改,其中 Point是現有的類。我們可以通過編寫一個聲明方面來實現這一點,該方面聲明Point Point類 具有一個實例字段Observers,該字段 跟蹤 正在觀察Point的Screen對象 。

2. The AspectJ Language

3. Examples

4. Idioms

5. Pitfalls

A. AspectJ Quick Reference

B. Language Semantics

C. Implementation Notes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章