使用
AspectJ(也就是AOP)的動機是發現那些使用傳統的編程方法無法很好處理的問題。考慮一個要在某些套用中實施安全策略的問題。安全性是貫穿於系統所有模組間的問題,每個模組都需要套用安全機制才能保證整個系統的安全性,很明顯這裡的安全策略的實施問題就是一個橫切關注點,使用傳統的編程解決此問題非常的困難而且容易產生差錯,這就正是AOP發揮作用的時候了。@AspectJ 使用了Java5 的註解,可以將切面聲明為普通的Java類。
傳統的面向對象編程中,每個單元就是一個類,而類似於安全性這方面的問題,它們通常不能集中在一個類中處理因為它們橫跨多個類,這就導致了代碼無法重用,可維護性差而且產生了大量代碼冗餘,這是我們不願意看到的。
面向切面編程的出現正好給處於黑暗中的我們帶來了光明,它針對於這些橫切關注點進行處理,就好像面向對象編程處理一般的關注點一樣。而作為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
使用了三個暴露的環境,一個名為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對象的引用,從而實現這個功能。
然後我們可以定義一個切點stateChanges決定我們想要觀察什麼並且提供一個after通知定義當觀察到變化時我們想要做什麼。
注意無論是Sreen還是Point的代碼都沒有被修改,所有的新功能的加入都在方面中實現了,很酷吧!
方面
方面以橫切模組單元的形式包裝了所有的切點、通知和類型間聲明。這非常像Java語言的類。實際上,方面也可以定義自己的方法,欄位和初始化方法。像類一樣一個方面也可以用abstract關鍵字聲明為抽象方面,可以被子方面繼承。在AspectJ中方面的設計實際上使用了單例模式,預設情況下,它不能使用new構造,但是可以使用一個方法實例化例如方法aspectOf()可以獲得方面的實例。所以在方面的通知中可以使用非靜態的成員欄位。