前一篇文章介紹了面向方面編程和關注點分離的概念,解釋了這種概念如何在方面構造的幫助下增強軟件的模塊化,以及如何使用它來補充面向對象編程。方面代表模塊化的單元,並且由切點(何處)、建議(什麼)以及類型間聲明(在這個新的方面補充對象模型)組成。有許多技術可以將關注點編織進應用程序,在當今的Java領域中,最常用的技術是字節碼測試,在AspectWerkz和ASPectJ(從1.1版開始)中實現了這種技術。
但是,這種AOP實現方式具有幾個缺點,本系列的第1篇文章對此進行了詳細解釋。盡管在字節碼測試領域還有很大的發展余地(包括Java 5中的JVMTI/JSR-163測試代理規范和高效字節碼操作庫,比如ObjectWeb ASM),但字節碼測試代價不菲。此外,已經證明,使用字節碼測試實現AOP是不完善的。例如,如果不采用非常特殊且效率低下的解決方案,就無法通過切點匹配反射式方法調用或get和set字段。總的來說,所有基於字節碼測試的產品都受到字節碼測試技術相關問題的影響,而且隨著這種技術的普及,問題將逐漸增加。
所有這些缺點促使JRockit團隊提出了JVM對AOP的支持。其目標是盡可能全面地實現當前的AOP語義,同時不把JVM限制在某個特定的面向方面框架的語言細節和編程模型上。
本文通過具體的代碼示例介紹該API,然後描述其好處及未來的發展方向。
我們的動機
讓我們快速地回顧引入JVM的AOP支持的技術動機。
JVM編織是對上面提到的問題最自然的解決方案。為了說明其原因,我們將引入兩個例子,它們說明JVM已經完成了編織所涉及的大多數工作:當加載一個類時,JVM讀取字節碼,建立為Java.lang.reflect.* API進行服務所需的數據;另一個例子是方法調度。目前的JVM將方法或代碼塊的字節碼編譯為更高級、效率也更高的構造和執行流(在適用代碼內聯的地方進行代碼內聯)。由於HotSwap API的需要,JRockit JVM(可能還包括其他JVM)還會記錄哪個方法調用了其他方法,因此如果在運行時重新定義某個類,那麼在所有期望的位置(內聯的或非內聯的),類中定義的方法主體仍然可以進行熱交換。
因此,不必為了編織進一個建議調用而修改字節碼,比如說,在特定的方法調用之前。JVM實際上可以掌握關於這個建議調用的知識,它會在任何匹配的聯結點上對此建議進行調度,然後再調度實際的方法。
由於不接觸字節碼,立即可以獲得以下好處:
不會由於字節碼測試而產生啟動開銷。
對於在任何位置、任何時間、以遞增式開銷添加和刪除建議的完全的運行時支持。
對建議的反射式調用的隱式支持。
不需要將類模型復制到特定於框架的某些結構,因此減少了額外的內存占用。
與JVMDI_EVENT_METHOD_ENTRY或JVMDI_EVENT_FIELD_Access等JVMDI規范中定義的眾所周知的C級別事件相比,這種方式有很大區別。在JVMDI中,必須首先處理C級別API,這使得它對於大多數開發人員來說有些復雜,而且難以分發。其次,規范沒有提供細粒度的聯結點匹配機制,而是要求預定所有這樣的事件。這仍然會導致顯著的開銷,因此不得不進行調試。
我們的方法
我們想讓您先了解一下如何在JVM中添加AOP支持。關鍵之處在於我們在Java API級別上提供了動作調度和預定(下面會詳細描述)。因此,您可以寫出下面這樣的代碼:
Weaver w = WeaverFactory.getWeaver();
Method staticActionMethod =SimpleAction.class.getDeclaredMethod
("simpleStaticAction",new Class[0]//no arguments);
MethodSubscription ms = new MethodSubscription
(/* where to match*/,InsertionType.BEFORE,staticActionMethod);
w.addSubscription(ms);
如您所見,我們提供了一個可訪問的JVM API,可以用它來實現更傳統的AOP方法。這為解決前面提到的傳統AOP實現問題提供了極大的靈活性,而且也使其他使用方式成為可能。下面幾節將詳細介紹這個API。
動作調度和預定
JRockit JVM AOP支持公開了一個Java API,它與JVM方法調度和對象模型組件緊密集成在一起。為了確保不使JVM被限制在當前或未來的任何特定於AOP的技術方向上,我們決定實現一個動作調度和預定模型。
這個API使您能夠在指定的切點上描述定義良好的預定,這樣就能夠注冊JVM將要調度的動作。動作由以下組件組成:
一個常規Java方法——我們稱之為動作方法,對於每個匹配這個預定的聯結點,都將調用這個方法。
一個可選的動作實例,在這個實例上調用動作方法。
一組可選的參數級注釋,它們向JVM指出動作方法期望從調用堆棧獲得哪些參數。
動作還可以分為before動作、after returning動作、after throwing動作或者instead-of動作(類似於AOP的“around”概念)。
為了調用這個API,必須獲得一個jrockit.ext.weaving.Weaver實例的句柄。這個編織器實例根據它的調用者上下文來控制允許進行哪些操作。例如,在容器級編織器可以預定特定於應用程序的聯結點時,用戶可能不希望部署在應用服務器中的應用程序創建編織器,從而預定某些容器級或特定於JDK的聯結點的動作方法。這種編織器可見性理念反映了底層類加載器的委托模型。
我們簡單介紹一下這些構造如何映射到常規的AOP構造,這有助於理解這個模型:
預定可以視為一個有類型的聯結點,或者就是一個有類型的聯結點(字段get()、set()、方法call()等等),加上一個within()/withincode()切點。
動作實例可以視為方面實例。
動作方法可以視為建議。
熟悉AOP的讀者可能已經看出,要想用這個JVM級API實現一個完整的AOP框架,還需要進行一些開發,包括一個(按照規定)管理方面實例化模型的中間層、cflow()切點的實現以及切點的完全合成和正交的實現。
API細節:動作方法
動作方法(與AOP的建議概念相似)就像(作為方面的)常規類的常規Java方法。它可以是static方法,也可以是成員方法。它的返回類型必須符合某些隱式約定,而且before動作的返回類型應該是void。對於instead-of動作(類似於AOP的around建議語義),其返回類型還是作為動作調用結果的堆棧的類型。
動作方法可以有參數,參數的注釋進一步控制上下文公開,如下面的代碼示例所示:
import Java.lang.reflect.*;
import jrockit.ext.weaving.*;
public class SimpleAction
{ public static void simpleStaticAction()
{out.println("hello static action!");
}
public void simpleAction() {out.println("hello action!");
}
public void simpleAction(@CalleeMethod WMethod calleeM,@CallerMethod WMethod callerM)
{out.println(callerM.getMethod().getName());
out.println(" calling ");
out.println(calleeM.getMethod().getName());
}
}
該代碼示例引入了jrockit.ext.weaving.WMethod類。該類用作java.lang.reflect.Method、java.lang.reflect.Constructor和類的靜態初始化器(它在Java.lang.reflect.*中沒有出現)的包裝器。這與ASPectJ JoinPoint.StaticPart.getSignature()抽象化相似。
下面是當前定義的注釋及其含義。
[[The No.1 Picture.]]
為了支持instead-of,並能夠決定是否沿著截取鏈前進(就像在AOP中通過JoinPoint.proceed()概念實現),我們引入了jrockit.ext.weaving.InvocationContext構造,如下所示:
import jrockit.ext.weaving.*;
public class InsteadOfAction {
public Object instead(
InvocationContext jp,
@CalleeMethod Method callee) {
return jp.proceed();
}
}
API的細節:動作實例和動作類型
正如前面代碼示例中所示,動作方法可以是靜態的,也可以不是。如果動作方法不是靜態的,那麼就必須傳遞一個動作實例,JVM在這個實例上調用動作方法。
其語法風格與Java開發人員使用Java.lang.reflect.Method.invoke(null/*static method*/, .../*args*/)對方法進行反射式調用一樣。但是,利用JVM的AOP支持,底層的動作調用根本不涉及任何反射。
允許用戶控制動作實例,就會產生有趣的用例。例如,可以實現一個簡單的委托模式,在運行時用另一個實現替換整個動作實例,而不涉及JVM的內部組件。
注意,這將有助於(按照規定)實現AOP方面實例化模型,比如issingleton()、pertarget()、perthis()、percflow()等等,同時不會將JVM API限制在某些預定義的語義上。
在將預定注冊到編織器實例之前,賦予它一個類型作為建議類型:before、instead-of、after-returning或after-throwing。
可以編寫下面這樣的代碼來創建預定:
// Get a Weaver instance that will act as a
// container for the subscription(s) we create
Weaver w = WeaverFactory.getWeaver();
// regular Java.lang.reflect is used to refer
// to the action method "simpleStaticAction()"
Method staticActionMethod =
SimpleAction.class.getDeclaredMethod(
"simpleStaticAction",
new Class[0]//no arguments
);
MethodSub