面向切面編程(AOP)初探
面向?qū)ο缶幊掏ㄟ^設(shè)計和語言本身提供的模塊化、封裝、繼承、多態(tài)來實現(xiàn)軟件復(fù)用。盡管OOP在建模以及實現(xiàn)復(fù)雜軟件方面非常成功,它仍然有一些問題。面向切面編程(AOP)被認(rèn)為是一項有前途的新技術(shù),它通過對交叉業(yè)務(wù)的分隔來實現(xiàn),而這在面向?qū)ο缶幊汤锖茈y做到。本文通過一個新的范例介紹AOP的基本概念。
面向?qū)ο缶幊?Object Oriented Programming
今天,面向?qū)ο缶幊桃呀?jīng)成為主流的編程模式,在這里,現(xiàn)實問題被分解為一個個的包含數(shù)據(jù)和行為的對象。
在大型工程實踐中,程序員發(fā)現(xiàn)在模塊中越來越難以分離交叉業(yè)務(wù),他們的代碼也變得更加難維護(hù)。對程序設(shè)計的一絲改動都會引發(fā)大量不相關(guān)模塊的改動。
交叉業(yè)務(wù) Crosscutting Concerns
一個交叉業(yè)務(wù)的例子是“日志”,日志在分布式系統(tǒng)中經(jīng)常被用來記錄方法調(diào)用,以輔助調(diào)試。假設(shè)我們在每個函數(shù)開始前和結(jié)束后都寫日志,這會使我們對所有包含方法的類做“橫切”(crosscutting)。其他典型的交叉業(yè)務(wù)包括:上下文敏感的錯誤處理,性能優(yōu)化,以及設(shè)計模式。
交叉業(yè)務(wù)可能出現(xiàn)在某些程序中,尤其是那些大型程序中。然而另一方面,對系統(tǒng)的重新設(shè)計可以將交叉業(yè)務(wù)轉(zhuǎn)換成對象。AOP假定交叉業(yè)務(wù)會出現(xiàn)在程序中,并無法從重構(gòu)中被剔除出去。
面向切面編程 Aspect Oriented Programming
面向切面的編程AOP是一項新的技術(shù),它將交叉業(yè)務(wù)分離出來,作為獨立單元——切面——處理。切面即是交叉業(yè)務(wù)的模塊化實現(xiàn),它封裝了對各個類都有影響的行為,作為新的可重用的模塊。利用AOP,我們可以用OO編程語言(如Java)開始項目,然后我們單獨使用切面處理交叉業(yè)務(wù)。最后,代碼和切面一起通過編織器(aspect weaver)組織成最終可執(zhí)行文件。圖1說明了"編織器"工作過程。注意,原始的代碼不需要知道切面的任何功能;只要除去切面代碼并重新編譯,就能得到初始代碼的功能。
AOP是一種編程概念,因此它并未綁定到任何特定的語言。事實上,它對所有單獨的、垂直分解式(譯注:AOP通常被認(rèn)為是橫向分解)的語言(不僅是OO語言)都有幫助。AOP在不同語言都有實現(xiàn)(如 C++, Smalltalk, C#, C, Java).
當(dāng)然,受益最大的還是Java語言。下面是一些支持Java AOP的工具:
◆AspectJ
◆AspectWerkz
◆Hyper/J
◆JAC
◆JMangler
◆MixJuice
◆PROSE
◆ArchJava
由Xerox PARC所創(chuàng)建的AspectJ被認(rèn)為是Java語言在AOP方面的一個擴展,是專門為面向切面的編程而生的。本文下面部分主要涉及AspectJ.#p#
連接點,切入點,通知和引入 Join points, Pointcut, Advice, and Introduction
就如OOP的概念包含繼承、封裝、多態(tài)一樣,組成AOP的概念是連接點,切入點,通知和引入(Join points, Pointcut, Advice, and Introduction)。為更好的理解這些術(shù)語,我們看一下下面的例子。
- public class TestClass {
- public void sayHello () {
- System.out.println ("Hello, AOP");
- }
- public void sayAnyThing (String s) {
- System.out.println (s);
- }
- public static void main (String[] args) { sayHello ();
- sayAnyThing ("ok");
- }
- }
我們的Java代碼保存在TestClass.java,假設(shè)我們想用切面做如下修改:
在對TestClass.sayHello()方法調(diào)用之前和之后,都打印一行信息;檢查TestClass.sayAnyThing() 方法的參數(shù),至少3個字符才能執(zhí)行
下面就是AspectJ 的實現(xiàn)。
- public aspect MyAspect {
- public pointcut sayMethodCall (): call (public void
- TestClass.say*() );
- public pointcut sayMethodCallArg (String str): call
- (public void TestClass.sayAnyThing (String))
- && args(str);
- before(): sayMethodCall() {
- System.out.println("\n TestClass." +
- thisJoinPointStaticPart.getSignature().getName() +
- "start..." );
- }
- after(): sayMethodCall() {
- System.out.println("\n TestClass." +
- thisJoinPointStaticPart.getSignature().getName() +
- " end...");
- }
- before(String str): sayMethodCallArg(str) {
- if (str .length() < 3) {
- System.out.println ("Error: I can't say words less than 3
- characters");
- return;
- }
- }
- }
Line 1 定義了一個aspect,就像我們定義Java 類。跟任何Java類一樣,aspect也可以擁有成員變量和方法,另外它還可以包含切入點(pointcuts),通知(advices)和引入(introductions).
Lines 2和Line 3指定我們的修改在TestClass什么地方起作用。按AspectJ術(shù)語,我們定義了2個切入點(pointcuts)。為了弄清楚切入點(pointcut)是什么意思,我們需要先定義連接點(join points).
連接點(join points)表示在程序執(zhí)行過程中預(yù)先定義的“點”,AspectJ 中典型的連接點包括:方法或構(gòu)造器的調(diào)用,方法或構(gòu)造器的執(zhí)行,字段的讀取,異常處理,以及靜態(tài)或動態(tài)的初始化。本文例子中,我們定義了2處連接點:對TestClass.sayHello方法的調(diào)用及對TestClass.sayAnyThing方法的調(diào)用。
切入點(Pointcut)是符合預(yù)定義規(guī)范的連接點(a set of join points)的集合,這是一個語言上的構(gòu)造概念。 規(guī)范可以是明確的的函數(shù)名,也可以是包含通配符的函數(shù)名。
public pointcut sayMethodCall (): call (public void
TestClass.say*() );
上面一行,我們定義了一個切入點(pointcut),叫做 sayMethodCall,它會檢查所有對TestClass.sayHello方法的調(diào)用。另外,它同樣會檢查TestClass 類里所有以"say"開頭,參數(shù)為空的公共方法(舉個例子:TestClass.sayBye).
切入點(Pointcuts)用來定義“通知” (advice). AspectJ 的advice用來定義在連接點執(zhí)行之前、之中、之后的額外代碼。在我們的例子中,line 4-6 和line7-9 分別定義了對第一個切入點執(zhí)行之前和之后的通知。Lines10-15定義了對第二個切入點的通知,即設(shè)置TestClass.sayAnyThing 方法執(zhí)行的一個前置條件。
切入點pointcuts和通知advice能讓你影響程序的動態(tài)執(zhí)行部分,與此不同,引入(introduction)允許切面修改程序中靜態(tài)的部分。通過引入(introduction), 切面可以為類添加新的方法及變量,聲明類實現(xiàn)的接口,或?qū)⒉东@的異常轉(zhuǎn)為未捕獲的異常。 Introduction和一個更為實用的AOP的例子是我未來一篇文章的主題。
AspectJ 編譯器
回到開頭,你需要從AspectJ 的官方網(wǎng)站上下載它的最新版本并安裝它(免費的),編譯和運行我們的例子非常簡單:
ajc MyAspect.aj TestClass.java
java TestClass
值得注意的是,Java源代碼TestClass.java 沒有任何改動。你只要使用Java編譯器重新編譯它就能得到最初的原始程序功能。
【編輯推薦】