跟我學AspectJ(二)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
本文繼續前篇的內容,將介紹AspectJ的應用範圍以及AspectJ的部分基本語言。
AspectJ應用範圍
如前所述,AspectJ可以用於應用開發的不同階段。下面討論不同階段的AspectJ的具體應用情況。
開發型方面(Development Aspects)
開發方面可以很容易的從真正的產品中刪除。而產品方面則被可用於開發過程和生產過程,但是僅僅影響某幾個類。
這一部分將通過幾個例子說明方面在Java應用的開發階段是如何使用的。這些方面包括調試、測試和性能檢測等工作。方面定義的行爲範圍包括簡單的代碼跟蹤、測試應用的內在聯繫等等。使用AspectJ不僅使得模塊化這些功能變爲可能,同時也使得根據需要打開和關閉這些功能變成可能。
代碼跟蹤(Tracing)
首先讓我們看看如何增加一個程序內部工作的可視性。我們定義一個簡單的方面用於代碼跟蹤並且在每個方法調用時輸出一些信息。在前一篇的圖形編輯例子中,這樣的方面可能僅僅簡單的跟蹤什麼時候畫一個點。
aspect SimpleTracing {
pointcut tracedCall():
call(void FigureElement.draw(GraphicsContext));
before(): tracedCall() {
System.out.println("Entering: " + thisJoinPoint);
}
}
代碼利用了thisJoinPoint變量。在所有的通知體內,這個變量將與描述當前連接點的對象綁定。所以上述代碼在每次一個FigureElement對象接受到draw方法時輸出如下信息:
Entering: call(void FigureElement.draw(GraphicsContext))
通常我們在調式程序時,會在特定的地方放置幾條輸出語句,而當調試結束時還需要找到這些代碼段將它們刪除,這樣做不但使我們的代碼很難看而且很費時間。而使用AspectJ我們可以克服以上的兩個問題,我們可以通過定義切點捕捉任何想要觀察的代碼段,利用通知可以在方面內部書寫輸出語句,而不需要修改源代碼,當不在需要跟蹤語句的時候還可以很輕鬆的將方面從應用中刪除並重新編譯代碼即可。
前提條件和後續條件(Pre-and Post-Conditions)
許多的程序員使用按契約編程(Design by Contract)的形式。這種形式的編程需要顯式的前提條件測試以保證方法調用是否合適,還需要顯式的後續條件測試保證方法是否工作正常。AspectJ使得可以模塊化地實現這兩種條件測試。例如下面的代碼
aspect PointBoundsChecking {
pointcut setX(int x):
(call(void FigureElement.setXY(int, int)) && args(x, *))
|| (call(void Point.setX(int)) && args(x));
pointcut setY(int y):
(call(void FigureElement.setXY(int, int)) && args(*, y))
|| (call(void Point.setY(int)) && args(y));
before(int x): setX(x) {
if ( x < MIN_X || x > MAX_X )
throw new IllegalArgumentException("x is out of bounds.");
}
before(int y): setY(y) {
if ( y < MIN_Y || y > MAX_Y )
throw new IllegalArgumentException("y is out of bounds.");
}
}
它實現了邊界檢測功能。當FigureElement對象移動時,如果x或y的值超過了定義的邊界,程序將會拋出IllegalArgumentException異常。
合同實施(Contract Enforcement)
基於屬性的橫切機制在定義更加複雜的合同實施上非常有用。一個十分強大的功能是它可以強制特定的方法調用只出現在對應的程序中,而在其他程序中不出現。例如,下面的方面實施了一個限制,使得只有在知名的工廠方法中才能向註冊並添加FigureElement對象。實施這個限制的目的是爲了確保沒有任何一個FigureElement對象被註冊多次。
static aspect RegistrationProtection {
pointcut register(): call(void Registry.register(FigureElement));
pointcut canRegister(): withincode(static * FigureElement.make*(..));
before(): register() && !canRegister() {
throw new IllegalAccessException("Illegal call " + thisJoinPoint);
}
}
這個方面使用了withincode初始切點,它表示在FigureElement對象的工廠方法(以make開始的方法)體內出現的所有連接點。在before通知中聲明一個異常,該通知用於捕捉任何不在工廠方法代碼內部產生的register方法的調用。該通知在特定連接點處拋出一個運行時異常,但是AspectJ能做地更好。使用declare error的形式,我們可以聲明一個編譯時的錯誤。
static aspect RegistrationProtection {
pointcut register(): call(void Registry.register(FigureElement));
pointcut canRegister(): withincode(static * FigureElement.make*(..));
declare error: register() && !canRegister(): "Illegal call"
}
當使用這個方面後,如果代碼中存在定義的這些非法調用我們將無法通過編譯。這種情況只出現在我們只需要靜態信息的時候,如果我們需要動態信息,像上面提到的前提條件實施時,就可以利用在通知中拋出帶參數的異常來實現。
配置管理(Configuration Management)
AspectJ的配置管理可以使用類似於make-file等技術進行處理。程序員可以簡單的包括他們想要的方面進行編譯。不想要任何方面出現在產品階段的開發者也可以通過配置他們的make-file使用傳統的Java編譯器編譯整個應用。
產品型方面(Production Aspects)
這一部分的方面例子將描述方面用於生產階段的應用。產品方面將嚮應用中加入功能而不僅僅爲程序的內部工作增加可視性。
改變監視(Change Monitoring)
在第一個例子,方面的角色是用於維護一位數據標誌,由它說明對象從最後一次顯示刷新開始是否移動過。在方面中實現這樣的功能是十分直接的,testAndClear方法被顯示代碼調用以便找到一個圖形元素是否在最近移動過。這個方法返回標誌的狀態並將它設置爲假。切點move捕捉所有能夠是圖形移動的方法調用。After通知截獲move切點並設置標誌位。
aspect MoveTracking {
private static boolean dirty = false;
public static boolean testAndClear() {
boolean result = dirty;
dirty = false;
return result;
}
pointcut move():
call(void FigureElement.setXY(int, int)) ||
call(void Line.setP1(Point)) ||
call(void Line.setP2(Point)) ||
call(void Point.setX(int)) ||
call(void Point.setY(int));
after() returning: move() {
dirty = true;
}
這個簡單例子同樣說明了在產品代碼中使用AspectJ的一些好處。考慮使用普通的Java代碼實現這個功能:將有可能需要包含標誌位,testAndClear以及setFlag方法的輔助類。這些方法需要每個移動的圖形元素包含一個對setFlag方法的調用。這些方法的調用就是這個例子中的橫切關注點。
·顯示的捕捉了橫切關注點的結構
·功能容易拔插
·實現更加穩定
傳遞上下文(Context Passing)
橫切結構的上下文傳遞在Java程序中是十分複雜的一部分。考慮實現一個功能,它允許客戶設置所創建的圖形對象的顏色。這個需求需要從客戶端傳入一個顏色或顏色工廠。而要在大量的方法中加入一個參數,目的僅僅是爲傳遞上下文信息這種不方便的情況是所有的程序員都十分熟悉的。
使用AspectJ,這種上下文的傳遞可以使用模塊化的方式實現。下面代碼中的after通知僅當一個圖形對象的工廠方法在客戶ColorControllingClient的某個方法控制流程中調用時才運行。
aspect ColorControl {
pointcut CCClientCflow(ColorControllingClient client):
cflow(call(* * (..)) && target(client));
pointcut make(): call(FigureElement Figure.make*(..));
after (ColorControllingClient c) returning (FigureElement fe):
make() && CCClientCflow(c) {
fe.setColor(c.colorFor(fe));
}
}
這個方面僅僅影響一小部分的方法,但是注意該功能的非AOP實現可能 需要編輯更多的方法。
提供一致的行爲(Providing Consistent Behavior)
接下來的例子說明了基於屬性的方面如何在很多操作中提供一致的處理功能。這個方面確保包com.bigboxco的所有公共方法記錄由它們拋出的任何錯誤。PublicMethodCall切點捕捉包中的公共方法調用, after通知在任何一個這種調用拋出錯誤後運行並且記錄下這個錯誤。
aspect PublicErrorLogging {
Log log = new Log();
pointcut publicMethodCall():
call(public * com.bigboxco.*.*(..));
after() throwing (Error e): publicMethodCall() {
log.write(e);
}
}
在一些情況中,這個方面可以記錄一個異常兩次。這在com.bigboxco包內部的代碼自己調用本包中的公共方法時發生。爲解決這個問題,我們可以使用cflow初始切點將這些內部調用排除:
after() throwing (Error e) : publicMethodCall() && !cflow(publicMethodCall()) {
log.write(e);
}
結論
AspectJ是對Java語言的簡單而且實際的面向方面的擴展。僅通過加入幾個新結構,AspectJ提供了對模塊化實現各種橫切關注點的有力支持。向以有的Java開發項目中加入AspectJ是一個直接而且漸增的任務。一條路徑就是通過從使用開發方面開始再到產品方面當擁有了AspectJ的經驗後就使用開發可重用方面。當然可以選取其他的開發路徑。例如,一些開發者將從使用產品方面馬上得到好處,另外的人員可能馬上編寫可重用的方面。
AspectJ可以使用基於名字和基於屬性這兩種橫切點。使用基於名字橫切點的方面僅影響少數幾個類,雖然它們是小範圍的,但是比起普通的Java實現來說它們能夠減少大量的複雜度。使用基於屬性橫切點的方面可以有小範圍或着大範圍。使用AspectJ導致了橫切關注點的乾淨、模塊化的實現。當編寫AspectJ方面時,橫切關注點的結構變得十分明顯和易懂。方面也是高度模塊化的,使得開發可拔插的橫切功能變成現實。
AspectJ提供了比這兩部分簡短介紹更多的功能。本系列的下一章內容,The AspectJ Language,將介紹 AspectJ語言的更多細節和特徵。系列的第三章,Examples將通過一些完整的例子說明如何使用AspectJ。建議大家在仔細閱讀了接下來的兩章後再決定是否在項目中加入AspectJ。
更多信息
如果需要轉貼請寫名作者和出處。