面向方面編程(Aspect-Oriented Programming, AOP)是一個令人興奮的新模式。就開發軟件系統而言,它的影響力將會和有15到20年的面向對象一樣。面向方面編程和面向對象編程不但不是互相競爭的技術而且是可以很好的互補。面向對象編程主要用于為同一對象層次的公用行為建模。它的弱點是將公共行為應用于多個無關對象模型之間。而這恰恰是AOP適合的地方。AOP允許定義交叉的關系,那些關系應用于跨國分開的,非常不同的對象模型。AOP允許你層次化功能性而不是嵌入功能性,那使得代碼有更好的可度性和易于維護性。我喜歡認為OOP是自上而下的軟件開發,而AOP是自左而右的軟件開發,它們是完全直交的技術,并且互相很好的補充。
在OOP的工具里是繼承,封裝和多態,而AOP的組件是通知/攔截器,導言,元數據和pintcuts.讓我們看一下這些定義。
通知/攔截器
一個通知是一個邏輯,這個邏輯有特定的事件觸發。它是行為,這個行為能夠被插入在調用者和被調用者之間,在一個方法調用者和實際的方法之間。通知是AOP真正的關鍵。通知允許你去透明的應用一些事物,像日志和記錄到一個存在的對象模型。
在 JBoss AOP中,我們用攔截器是實現了通知。你能夠定義攔截器,它攔截方法調用,構造器調用和域訪問。后面,我們將闡明怎樣應用這些攔截器到一個存在的對象模型。
導言
導言是一個增加方法或者域到一個存在的類中的途徑。它們甚至允許你改變當前存在的類是顯的接口,并且引入一個混合的類,這個類是實現了新的接口。導言允許你帶入多繼承到一般的Java類。導言一個主要的用例是當你有一個方面,你想讓這個方面有一個運行時間借口時。你想應用你的方面跨越不同的對象層次,但是你仍然要應用開發者去能夠調用特定方面的APIs.
Apple apple = new Apple();
LoggingAPI logging = (LoggingAPI)apple;
Apple.setLoggingLevel(VERBOSE);
導言能夠是一個方法,它將一個新的API綁定到一個存在的對象模型。
元數據
元數據是能夠綁定到一個類的附加信息,在靜態或者運行時間。元數據更加有力力量的是,你能夠動態綁定元數據到一個給定的對象實例。元數據非常強大的,當你真正編寫應用于任何對象的一般方面,而邏輯需要知道制定類的信息時。在使用的一個好的元數據類比就是EJB規范。在EJB的XML發布描述符中,你需要定義基于每一個方法的事務屬性。應用服務器指導什么時候,什么地方開始,掛起或者提交一個事務,因為你在BEAN的XML的配置文件中的元數據內已經定義如方法:Required,RequiresNew,Support等等,它們綁定在你的EJB類和事務管理之間。
C#把元數據成為了這個語言的組成部分。XDoclet是另一個動作的元數據的例子。如果你曾經用過XDoclet生成過EJB文件和發布描述符,你就會知道元數據的力量。在JDK1.5中,當元數據被加入java語言中,JCP一致同意。(見JSR175)。盡管直到JSR175成為了事實,一個好的AOP框架也應該提供一種機制去定義在運行時間有效的類級元數據。
Pointcuts
如果攔截器,導言和元數據是AOP的特征,那么pointcuts就是粘合劑。Pointcuts告訴AOP框架,那些攔截器綁定到那些類, 什么原數據將應用于那些類或者那一個導言將被傳入那些類。Pointcuts定義各種AOP特征將怎樣應用于你應用中的類。
在動作中的AOP
例1.使用攔截器
JBoss 4.0帶了一個AOP框架。這個框架和JBoss應用服務器緊密地結合,但是你也能夠在你的應用中,單獨的運行它。直到你看了動作中看到它,你才會完全的理解這個概念,所以讓我們用一個來自于JBoss AOP的例子,來說明這個模塊所有的部分是如何一起工作的。在這章余下的部分,我們將建立一個例子來跟蹤使用AOP的框架。
定義一個攔截器
為了實現我們對于框架的跟蹤,我們必須作的第一件事是定義一個攔截器,它將作實際的工作。在JBOSS AOP中,所有的攔截器必須實現org.jboss.aop.Interceptor 接口。
public interface Interceptor
{
public String getName();
public InvocationResponse invoke(Invocation invocation)
throws Throwable;
}
在JBoss AOP中,被攔截的所有域,構造器和方法被轉成一般的invoke調用。方法的參數被填入一個Invocation對象,并且方法的返回值,域的存取或者構造器被填入一個InvocationResponse對象。這個Invocation對象也驅動這個攔截鏈。為了清楚地說明這個,讓我們看一下,在這個例子中,所有的對象是如何配合到一起的。
import org.jboss.aop.*;
import java.lang.reflect.*;
public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String message = null;
if (invocation.getType() == InvocationType.METHOD)
{
Method method = MethodInvocation.getMethod(invocation);
message = method: + method.getName();
}
else if (invocation.getType() == InvocationType.CONSTRUCTOR)
{
Constructor c = ConstructorInvocation
.getConstructor(invocation);
message = constructor: + c.toString();
}
else
{
// Do nothing for fields. Just too verbose.
//對于域什么也不做。太繁瑣。
return invocation.invokeNext();
}
System.out.println(Entering + message);
// Continue on. Invoke the real method or constructor.
// 繼續。調用真正的方法或者構造器
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving + message);
return rsp;
}
}
上面的攔截器將攔截所有的對一個域,構造器或方法的調用。如果調用的類型是一個方法或者構造器,一個帶有方法或構造器簽名的消息將輸出到控制平臺。
綁定攔截器
好了,這樣我們就定義了攔截器。但是怎么綁定這個攔截器到實際的類?為了做這個,我們需要定義一個pointcut。對于JBoss AOP, pointcuts 是在一個XML文件中定義的。讓我們看一下這看起來象什么。
<?xml version="1.0" encoding="UTF-8">
<aop>
<interceptor-pointcut class="POJO">
<interceptors>
<interceptor class="TracingInterceptor" />
</interceptors>
</interceptor-pointcut>
</aop>
上面的pointcut綁定TracingInterceptor到一個叫做POJO的類。這看起來有一點麻煩;我們不得不為每一個想跟蹤的類創建一個pointcut嗎?幸運的是,interceptor-pointcut的類屬性可以用任何的正規表達式。所以如果你想跟蹤由JVM載入的類,類表達式將變為 .*。如果你僅僅想跟蹤一個特定的包,那么表達式將是com.acme.mypackge.*。
當單獨運行JBoss AOP時,任何符合 META-INF/jboss-aop.xml模式的XML文件將被JBoss AOP 運行時間所載入。如果相關的路徑被包含在任何JAR或你的CLASSPATH的目錄中,那個特定的XML文件將在啟動時,由JBoss AOP 運行時間所載入。
運行這個例子
我們將用上面定義的pointcut去運行例子。POJO類看起來如下:
public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
}
}
TracingInterceptor將攔截對main(),POJO()和helloWorld()的調用。輸出看起來如下:
Entering method: main
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Leaving method: main
你能夠在這里下載JBoss AOP和離子代碼。編譯和執行:
$ cd oreilly-aop/example1
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.
SystemClassLoader POJO
JBoss AOP 對綁定的攔截器做字節碼操作。因為沒有編譯步驟,AOP運行時間必須有ClassLoader的總控。如果你正運行在非JBoss應用服務器,你必須用JBoss制定的一個類載入器覆蓋系統的類載入器。
TraceingInterceptor不跟蹤域訪問,因為它有一點繁瑣。對于開發者,實現get()和set()方法去封裝域訪問是一個一般的實踐。如果TracingInterceptor能夠過濾出,并且不跟蹤這些方法,那是非常好的。這個例子顯示你能夠用JBoss AOP 元數據去實現基于任一方法的過濾。一般,元數據用于更復雜的事情,如定義事務屬性,每個方法的安全角色或者持久性映射,但是這個例子應該足夠說明元數據能夠怎樣用在 AOP使能的應用中。
定義類的元數據
為了增加這個過濾功能,我們將提供一個標志,你能夠用這個標著去關閉跟蹤。我們將回到我們的AOP的XML文件去定義標簽,那將刪除對get()和set()方法的跟蹤。事實上,對于main()函數的跟蹤毫無意義,所以我們也過濾出它。
<?xml version="1.0" encoding="UTF-8">
<aop>
<class-metadata group="tracing" class="POJO">
<method name="(get.*)|(set.*)">
<filter>true</filter>
</method>
<method name="main">
<filter>true</filter>
</method>
</class-metadata>
</aop>
上面的XML定義了一組叫做tracing的屬性。這個過濾屬性將綁定到每一個以get或者set開始的方法上。正則表達式格式用JDK-1.4定義的表達式。元數據通過Invocation對象,在TracingInterceptor內訪問。
訪問Metadata
為了用元數據,它在運行時間必須是可達的。類的元數據是通過Invocation對象可達的。為了在我們的例子使用它,TracingInterceptor必須要修改一點點。
public class TracingInterceptor implements Interceptor
{
public String getName() { return TracingInterceptor; }
public InvocationResponse invoke(Invocation invocation)
throws Throwable
{
String filter=(String)invocation.getMetaData(tracing, filter);
if (filter != null && filter.equals(true))
return invocation.invokeNext();
String message = null;
if (invocation.getType() == InvocationType.METHOD)
{
Method method = MethodInvocation.getMethod(invocation);
message = method: + method.getName();
}
else if (invocation.getType() == InvocationType.CONSTRUCTOR)
{
Constructor c = ConstructorInvocation
.getConstructor(invocation);
message = constructor: + c.toString();
}
else
{
// Do nothing for fields. Just too verbose.
return invocation.invokeNext();
}
System.out.println(Entering + message);
// Continue on. Invoke the real method or constructor.
InvocationResponse rsp = invocation.invokeNext();
System.out.println(Leaving + message);
return rsp;
}
}
運行例子2: POJO類將擴展一點,增加get()和set()方法。
public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
private int counter = 0;
public int getCounter() { return counter; }
public void setCounter(int val) { counter = val; }
public static void main(String[] args)
{
POJO pojo = new POJO();
pojo.helloWorld();
pojo.setCounter(32);
System.out.println(counter is: + pojo.getCounter());
}
}
TracingInterceptor將攔截對main(),POJO()和helloWorld()調用。輸出應該看起來如下:
Entering constructor: public POJO()
Leaving constructor: public POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
你能夠在這里下載JBoss AOP和離子代碼。編譯和執行: $ cd oreilly-aop/example2
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.
SystemClassLoader POJO
例子3.使用導言
如果我們能夠為特定的實例關閉和打開,那將很酷。JBoss AOP有一個API,他綁定元數據到一個對象實例,但是讓我們偽裝一個實際的跟蹤API是一個更好的方案。在這例子中,我們通過用一個導言,將改變POJO類的本身的定義。我們將強制POJO類去實現一個跟蹤借口和提供混合類,這個混合類處理新的跟蹤API。這將是跟蹤借口:
public interface Tracing
{
public void enableTracing();
public void disableTracing();
}
定義一個混合的類
Tracing接口將在混合類中實現。當一個POJO是實例時,一個混合對象混合類將綁定到POJO類。下面是實現:
import org.jboss.aop.Advised;
public class TracingMixin implements Tracing
{
Advised advised;
Public TracingMixin(Object obj)
{
this.advised = (Advised)obj;
}
public void enableTracing()
{
advised._getInstanceAdvisor().getMetaData().addMetaData(
"tracing", "filter", true);
}
public void disableTracing()
{
advised._getInstanceAdvisor().getMetaData().addMetaData(
"tracing", "filter", false);
}
}
enableTracing()方法綁定filter屬性到對象實例。在disableTracing()方法作同樣的事,但是制定filter屬性為false。這兩個方法是元數據能夠怎么樣用于超過一個類級別。元數據也能夠實例級的應用。元數據應用在實例級別。
綁定一個導言
好了,所以我們定義跟蹤接口,并且實現這個混合類。下一步是應用導言到POJO類。像攔截器,我們必須在XML中定義一個ponitcut。讓我們看一下這項什么。
<?xml version="1.0" encoding="UTF-8">
<aop>
<introduction-pointcut class="POJO">
<mixin>
<interfaces>Tracing</interfaces>
<class>TracingMixin</class>
<construction>new TracingMixin(this)</construction>
</mixin>
</introduction-pointcut>
</aop>
上面的pointcuts將強制POJO類實現Tracing接口?,F在,當一個POJO實例被初始化,一個TracingMixin也將被實例化。TracingMixin被初始化的途徑被定義在標簽中。你能夠把想要的任一行Java代碼放入在標簽中。
運行例子3
POJO類為了顯示TracingAPI怎么被訪問,它已經被擴展了一點。TracingInterceptor仍然和例子2一樣。
public class POJO
{
public POJO() {}
public void helloWorld() { System.out.println(Hello World!); }
public static void main(String[] args)
{
POJO pojo = new POJO();
Tracing trace = (Tracing)this;
pojo.helloWorld();
System.out.println("Turn off tracing.");
trace.disableTracing();
pojo.helloWorld();
System.out.println("Turn on tracing.");
trace.enableTracing();
pojo.helloWorld();
}
}
注意我們轉換POJO到Tracing接口。輸出應該看起來這樣: Entering constructor: POJO()
Leaving constructor: POJO()
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
Turn off tracing.
Entering method: disableTracing
Leaving method: disableTracing
Hello World!
Turn on tracing.
Entering method: helloWorld
Hello World!
Leaving method: helloWorld
注意被增加到TracingInterceptor 中的interceptor-pointcut也應用到那些通過Tracing 導言導入的方法中。為了編譯和運行這個例子: $ cd oreilly-aop/example3
$ export CLASSPATH=.;jboss-common.jar;jboss-aop.jar;javassist.jar
$ javac *.java
$ java -Djava.system.class.loader=org.jboss.aop.standalone.
SystemClassLoader POJO
結論:面向方面編程對于軟件開發是一個強有力的新工具。為了使你的軟件開發過程更加動態和流暢,用JBoss4.0,你能夠實現你自己的攔截器,元數據和導言。更詳細的文檔參見站點www.jboss.org。