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