聲明
本文由starchu1981保留版權。
跟我學AspectJ(一)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
編者的話
關於AspectJ的開發資料好象目前還只有英文版的,而且還不是很多,這對於有興趣學習AOP而英語不是很好的開發人員來是一件很苦悶的事情,所以我決定總結翻譯一些有關AOP的Java實現AspectJ的使用和語法的文章,所以纔有了跟我學AspectJ這一系列作品的出現,本系列文章是基於Xerox公司的AspectJ產品所包含的編程指南爲基礎,並加入了我個人的一些理解和例子但是由於有關我還要準備IELTs考試,所以有可能每一篇的發表不會那麼的按時,另外由於本人的英語水平也不是太好,所以如果讀者發現一些翻譯技術上的問題,還請各位看官多多包涵,謝謝。
引語
許多軟件開發人員被面向方面編程技術(AOP)深深的吸引,但是苦於不知到如何使用這項技術。他們知道橫切關注點(Crosscutting concerns)的概念,而且也確實遇到了這方面的問題。但是問題是如何將AOP引入開發的過程中呢?通常會出現下面的問題:
·能在現有代碼上使用方面(aspect)嗎?
·使用了方面後能獲得怎樣的好處呢?
·怎樣在程序中找到方面?
·AOP的學習曲線是怎樣的呢?
·使用這項新技術的風險是什麼?
本文將重點討論在AspectJ環境下這些問題的答案。AspectJ是對於Java語言的面向方面的擴展。如何獲得AspectJ並配置請參見我在CSDN中的文章AspectJ安裝和配置指南 。
本文將針對AspectJ介紹一些基本概念並以代碼片段的形式說明這些概念(聲明一點,這些概念的中文命名十分奇怪,不具代表性,所以建議使用英文名錶示)。
當然選取新技術存在很大的風險,因爲通常新東西發展特別快,指不定那天完全變了。出於這個考慮許多的公司都對新技術十分保守。還好,方面可以用在開發的不同階段,而且可以在毫不改變原有代碼的情況下實行,所以如果覺得它不怎麼的,可以很輕鬆的去除它的影響。開發階段的方面可用於程序的調式,單元測試以及性能監測;而在產品階段的方面完全可以實現通常在Java應用中的橫切功能;當然還可以設計開發可重用的方面,很不錯哦!
那些有所顧慮的人們完全可以只使用開發階段的方面,它對一減輕原來很煩瑣的重複操作,例如調式時的代碼執行情況的追蹤。
AspectJ簡介
這一部分我將簡單介紹AspectJ中的一些基本的概念,如果熟悉它們的讀者可以跳過不看(說明:2002月的程序員雜誌有國專題介紹,我推薦大家看,十分不錯哦,我對於AOP和AspectJ興趣就始於那次的專題)。
本文使用例子的形式說明概念,這樣有助於大家的理解,本文使用簡單的圖形編輯系統的例子(採用了AspectJ附帶文檔中的例子),其UML圖如下
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
圖1 :FigureEditor例子的UML圖
AspectJ(也就是AOP)的動機是發現那些使用傳統的編程方法無法很好處理的問題。考慮一個要在某些應用中實施安全策略的問題。安全性是貫穿於系統所有模塊間的問題,每個模塊都需要應用安全機制才能保證整個系統的安全性,很明顯這裏的安全策略的實施問題就是一個橫切關注點,使用傳統的編程解決此問題非常的困難而且容易產生差錯,這就正是AOP發揮作用的時候了。
傳統的面向對象編程中,每個單元就是一個類,而類似於安全性這方面的問題,它們通常不能集中在一個類中處理因爲它們橫跨多個類,這就導致了代碼無法重用,可維護性差而且產生了大量代碼冗餘,這是我們不願意看到的。
面向方面編程的出現正好給處於黑暗中的我們帶來了光明,它針對於這些橫切關注點進行處理,就好象面向對象編程處理一般的關注點一樣。而作爲AOP的具體實現之一的AspectJ,它向Java中加入了連接點(Join Point)這個新概念,其實它也只是現存的一個Java概念的名稱而已。它向Java語言中加入少許新結構:切點(pointcut)、通知(Advice)、類型間聲明(Inter-type declaration)和方面(Aspect)。切點和通知動態地影響程序流程,類型間聲明則是靜態的影響程序的類等級結構,而方面則是對所有這些新結構的封裝。
一個連接點是程序流中指定的一點。切點收集特定的連接點集合和在這些點中的值。一個通知是當一個連接點到達時執行的代碼,這些都是AspectJ的動態部分。其實連接點就好比是程序中的一條一條的語句,而切點就是特定一條語句處設置的一個斷點,它收集了斷點處程序棧的信息,而通知就是在這個斷點前後想要加入的程序代碼。AspectJ中也有許多不同種類的類型間聲明,這就允許程序員修改程序的靜態結構、名稱、類的成員以及類之間的關係。AspectJ中的方面是橫切關注點的模塊單元。它們的行爲與Java語言中的類很象,但是方面還封裝了切點、通知以及類型間聲明。
動態連接點模型
任何面向方面編程的關鍵元素就是連接點模型。AspectJ提供了許多種類的連接點集合,但是本篇只介紹它們中的一個:方法調用連接點集(method call join points)。一個方法調用連接點捕捉對象的方法調用。每一個運行時方法調用都是一個不同的連接點,許多其他的連接點集合可能在方法調用連接點執行時運,包括方法執行時的所有連接點集合以及在方法中其他方法的調用。我們說這些連接點集合在原來調用的連接點的動態環境中執行。
切點
在AspectJ中,切點捕捉程序流中特定的連接點集合。例如,切點
call(void Point.setX(int))
捕捉每一個簽名爲void Point.setX(int)的方法調用的連接點,也就是說,調用Point對象的有一個整型參數的void setX方法。切點能與其他切點通過或(||)、與(&&)以及非(!)操作符聯合。例如 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 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()代替捕捉這些複雜的切點。
前面所說的切點都是基於顯示的方法簽名,它們稱爲基於名字(name-based)橫切。AspectJ還提供了另一種橫切,稱爲基於屬性(property-based)的橫切。它們可以使用通配符描述方法簽名,例如 call(void Figure.make*(..)) 捕捉Figure對象中以make開頭的參數列表任意的方法調用的連接點。而 call(public & Figure.*(..)) 則捕捉Figure對象中的任何公共方法調用的連接點。但是通配符不是AspectJ支持的唯一屬性,AspectJ中還有許多其他的屬性可供程序員使用。例如cflow,它根據連接點集合是否在其他連接點集合的動態環境中發生標識連接點集合。例如 cflow(move()) 捕捉被move()捕捉到的連接點集合的動態環境中發生的連接點。
通知
雖然切點用來捕捉連接點集合,但是它們沒有做任何事。要真正實現橫切行爲,我們需要使用通知機制。通知包含了切點和要在每個連連接點處執行的代碼段。AspectJ有幾種通知。
·前通知(Before Advice) 當到達一個連接點但是在程序進程運行之前執行。例如,前通知在方法實際調用之前運行,剛剛在方法的參數被分析之後。
Before() : move(){ System.out.println(“物體將移動了”);}
·後通知(After Advice) 當特定連接點處的程序進程執行之後運行。例如,一個方法調用的後通知在方法體運行之後,剛好在控制返回調用者之前執行。因爲Java程序有兩種退出連接點的形式,正常的和拋出異常。相對的就有三種後通知:返回後通知(after returning)、拋出異常後通知(after throwing)和清楚的後通知(after),所謂清楚後通知就是指無論是正常還是異常都執行的後通知,就像Java中的finally語句。
After() returning : move(){ System.out.println(“物體剛剛成功的移動了”);}
·在周圍通知(Around Advice) 在連接點到達後,顯示的控制程序進程是否執行(暫不討論)
暴露切點環境
切點不僅僅捕捉連接點,它還能暴露連接點處的部分執行環境。切點中暴露的值可以在通知體中聲明以後使用。通知聲明有一個參數列表(和方法相同)用來描述它所使用的環境的名稱。例如後通知
after(FigureElement fe,int x,int y) returning : somePointcuts { someCodes }
使用了三個暴露的環境,一個名爲fe的FigureElement對象,兩個整型變量x,y。通知體可以像使用方法的參數那樣使用這些變量,例如
after(FigureElement fe,int x,int y) returning : somePointcuts {
System.out.println(fe+”移動到(”+x+”,”+y+”)”);
}
通知的切點發布了通知參數的值,三個原生切點this、target和args被用來發布這些值/所以上述例子的完整代碼爲
after(FigureElement fe,int x,int y) returning : call(void FigureElement.setXY(int,int)
&& target(fe) && args(x,y) {
System.out.println(fe+”移動到(”+x+”,”+y+”)”);
}
目標對象是FigureElement所以fe是after的第一個參數,調用的方法包含兩個整型參數所以x和y爲after的第二和第三個參數。所以通知打印出方法setXY調用返回後對象移動到的點x和y。當然還可以使用命名切點完成同樣的工作,例如
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+”移動到(”+x+”,”+y+”)”);
}
類型間聲明
AspectJ的類型間聲明指的是那些跨越類和它們的等級結構的聲明。這些可能是橫跨多個類的成員聲明或者是類之間繼承關係的改變。不像通知是動態地操作,類型間聲明編譯時的靜態操作。考慮一下,Java語言中如何向一個一些的類中加入新方法,這需要實現一個特定接口,所有類都必須在各自內部實現接口聲明的方法,而使用AspectJ則可以將這些工作利用類型間聲明放在一個方面中。這個方面聲明方法和字段,然後將它們與需要的類聯繫。
假設我們想有一個Sreen對象觀察Point對象的變化,當Point是一個存在的類。我們可以通過書寫一個方面,由這個方面聲明Point對象有一個實例字段observers,用來保存所有觀察Point對象的Screen對象的引用,從而實現這個功能。
Aspect PointObserving{
Private Collection Point.observers=new ArrayList();
……
}
observers字段是私有字段,只有PointObserving能使用。因此,要在aspect中加入方法管理observers聚集。
Aspect PointObserving{
Private Collection Point.observers=new ArrayList();
Public static void addObserver(Point p,Screen s){
p.observers.add(s);
}
public static void removeObserver(Point p,Screen s){
p.observers.remove(s);
}
……
}
然後我們可以定義一個切點stateChanges決定我們想要觀察什麼並且提供一個after通知定義當觀察到變化時我們想要做什麼。
Aspect PointObserving{
Private Collection Point.observers=new ArrayList();
Public static void addObserver(Point p,Screen s){
p.observers.add(s);
}
public static void removeObserver(Point p,Screen s){
p.observers.remove(s);
}
pointcut stateChanges(Point p) : target(p) && call(void Point.set*(int));
after(Point p) : stateChanges(p){
Iterator it=p.observers.iterator();
While(it.hasNext()){
UpdateObserver(p,(Screen)it.next()));
}
}
private static void updateObserver(Point p,Screen s){
s.display(p);
}
}
注意無論是Sreen還是Point的代碼都沒有被修改,所有的新功能的加入都在方面中實現了,很酷吧!
方面
方面以橫切模塊單元的形式包裝了所有的切點、通知和類型間聲明。這非常像Java語言的類。實際上,方面也可以定義自己的方法,字段和初始化方法。像類一樣一個方面也可以用abstrace關鍵字聲明爲抽象方面,可以被子方面繼承。在AspectJ中方面的設計實際上使用了單例模式,缺省情況下,它不能使用new構造,但是可以使用一個方法實例化例如方法aspectOf()可以獲得方面的實例。所以在方面的通知中可以使用非靜態的成員字段。
例如
aspect Tracing {
OutputStream trace=System.out;
After() : move(){ trace.println(“物體成功移動”); }
}
總結
本文是本系列的第一篇文章,目的是讓讀者能夠對AOP的Java實現AspectJ有一個基本的認識,並且瞭解AspectJ中的幾個重要的概念連接點、切點、通知還有類型間聲明,下一篇將討論面向方面編程在開發階段以及產品階段的應用。
更多信息
參考資料
1.The AspectJTM Programming Guide http://www.eclipse.org/aspectj/
聲明
本文由starchu1981保留版權,如果需要轉貼請寫明作者和出處。