隨筆-1  評論-0  文章-3  trackbacks-0
          JRockit JVM對AOP的支持,第2部分

          時間:2005-10-24
          作者:Alexandre Vasseur,?Aspect Werkz,?Joakim Dahlstedt,?Jonas Bonér

          前一篇文章 介紹了面向方面編程和關注點分離的概念,解釋了這種概念如何在方面構(gòu)造的幫助下增強軟件的模塊化,以及如何使用它來補充面向?qū)ο缶幊獭7矫娲砟K化的單元,并且由切點(何處)、建議(什么)以及類型間聲明(在這個新的方面補充對象模型)組成。有許多技術可以將關注點編織進應用程序,在當今的Java領域中,最常用的技術是字節(jié)碼測試,在AspectWerkzAspectJ(從1.1版開始)中實現(xiàn)了這種技術。

            但是,這種AOP實現(xiàn)方式具有幾個缺點,本系列的第1篇文章對此進行了詳細解釋。盡管在字節(jié)碼測試領域還有很大的發(fā)展余地(包括Java 5中的JVMTI/JSR-163測試代理規(guī)范和高效字節(jié)碼操作庫,比如ObjectWeb ASM),但字節(jié)碼測試代價不菲。此外,已經(jīng)證明,使用字節(jié)碼測試實現(xiàn)AOP是不完善的。例如,如果不采用非常特殊且效率低下的解決方案,就無法通過切點匹配反射式方法調(diào)用或get和set字段。總的來說,所有基于字節(jié)碼測試的產(chǎn)品都受到字節(jié)碼測試技術相關問題的影響,而且隨著這種技術的普及,問題將逐漸增加。

            所有這些缺點促使JRockit團隊提出了JVM對AOP的支持。其目標是盡可能全面地實現(xiàn)當前的AOP語義,同時不把JVM限制在某個特定的面向方面框架的語言細節(jié)和編程模型上。

            本文通過具體的代碼示例介紹該API,然后描述其好處及未來的發(fā)展方向。

          我們的動機

            讓我們快速地回顧引入JVM的AOP支持的技術動機。

            JVM編織是對上面提到的問題最自然的解決方案。為了說明其原因,我們將引入兩個例子,它們說明JVM已經(jīng)完成了編織所涉及的大多數(shù)工作:當加載一個類時,JVM讀取字節(jié)碼,建立為java.lang.reflect.* API進行服務所需的數(shù)據(jù);另一個例子是方法調(diào)度。目前的JVM將方法或代碼塊的字節(jié)碼編譯為更高級、效率也更高的構(gòu)造和執(zhí)行流(在適用代碼內(nèi)聯(lián)的地方進行代碼內(nèi)聯(lián))。由于HotSwap API的需要,JRockit JVM(可能還包括其他JVM)還會記錄哪個方法調(diào)用了其他方法,因此如果在運行時重新定義某個類,那么在所有期望的位置(內(nèi)聯(lián)的或非內(nèi)聯(lián)的),類中定義的方法主體仍然可以進行熱交換。

            因此,不必為了編織進一個建議調(diào)用而修改字節(jié)碼,比如說,在特定的方法調(diào)用之前。JVM實際上可以掌握關于這個建議調(diào)用的知識,它會在任何匹配的聯(lián)結(jié)點上對此建議進行調(diào)度,然后再調(diào)度實際的方法。

            由于不接觸字節(jié)碼,立即可以獲得以下好處:

          • 不會由于字節(jié)碼測試而產(chǎn)生啟動開銷。
          • 對于在任何位置、任何時間、以遞增式開銷添加和刪除建議的完全的運行時支持。
          • 對建議的反射式調(diào)用的隱式支持。
          • 不需要將類模型復制到特定于框架的某些結(jié)構(gòu),因此減少了額外的內(nèi)存占用。

            與JVMDI_EVENT_METHOD_ENTRY或JVMDI_EVENT_FIELD_ACCESS等JVMDI規(guī)范中定義的眾所周知的C級別事件相比,這種方式有很大區(qū)別。在JVMDI中,必須首先處理C級別API,這使得它對于大多數(shù)開發(fā)人員來說有些復雜,而且難以分發(fā)。其次,規(guī)范沒有提供細粒度的聯(lián)結(jié)點匹配機制,而是要求預定所有這樣的事件。這仍然會導致顯著的開銷,因此不得不進行調(diào)試。

          我們的方法

            我們想讓您先了解一下如何在JVM中添加AOP支持。關鍵之處在于我們在Java API級別上提供了動作調(diào)度和預定(下面會詳細描述)。因此,您可以寫出下面這樣的代碼:

          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,可以用它來實現(xiàn)更傳統(tǒng)的AOP方法。這為解決前面提到的傳統(tǒng)AOP實現(xiàn)問題提供了極大的靈活性,而且也使其他使用方式成為可能。下面幾節(jié)將詳細介紹這個API。

          動作調(diào)度和預定

            JRockit JVM AOP支持公開了一個Java API,它與JVM方法調(diào)度和對象模型組件緊密集成在一起。為了確保不使JVM被限制在當前或未來的任何特定于AOP的技術方向上,我們決定實現(xiàn)一個動作調(diào)度和預定模型。

            這個API使您能夠在指定的切點上描述定義良好的預定,這樣就能夠注冊JVM將要調(diào)度的動作。動作由以下組件組成:

          • 一個常規(guī)Java方法——我們稱之為動作方法,對于每個匹配這個預定的聯(lián)結(jié)點,都將調(diào)用這個方法。
          • 一個可選的動作實例,在這個實例上調(diào)用動作方法。
          • 一組可選的參數(shù)級注釋,它們向JVM指出動作方法期望從調(diào)用堆棧獲得哪些參數(shù)。

            動作還可以分為before動作、after returning動作、after throwing動作或者instead-of動作(類似于AOP的“around”概念)。

            為了調(diào)用這個API,必須獲得一個jrockit.ext.weaving.Weaver實例的句柄。這個編織器實例根據(jù)它的調(diào)用者上下文來控制允許進行哪些操作。例如,在容器級編織器可以預定特定于應用程序的聯(lián)結(jié)點時,用戶可能不希望部署在應用服務器中的應用程序創(chuàng)建編織器,從而預定某些容器級或特定于JDK的聯(lián)結(jié)點的動作方法。這種編織器可見性理念反映了底層類加載器的委托模型。

            我們簡單介紹一下這些構(gòu)造如何映射到常規(guī)的AOP構(gòu)造,這有助于理解這個模型:

            ·預定可以視為一個有類型的聯(lián)結(jié)點,或者就是一個有類型的聯(lián)結(jié)點(字段get()、set()、方法call()等等),加上一個within()/withincode()切點。

            ·動作實例可以視為方面實例。

            ·動作方法可以視為建議。

            熟悉AOP的讀者可能已經(jīng)看出,要想用這個JVM級API實現(xiàn)一個完整的AOP框架,還需要進行一些開發(fā),包括一個(按照規(guī)定)管理方面實例化模型的中間層、cflow()切點的實現(xiàn)以及切點的完全合成和正交的實現(xiàn)。

          API細節(jié):動作方法

            動作方法(與AOP的建議概念相似)就像(作為方面的)常規(guī)類的常規(guī)Java方法。它可以是static方法,也可以是成員方法。它的返回類型必須符合某些隱式約定,而且before動作的返回類型應該是void。對于instead-of動作(類似于AOP的around建議語義),其返回類型還是作為動作調(diào)用結(jié)果的堆棧的類型。

            動作方法可以有參數(shù),參數(shù)的注釋進一步控制上下文公開,如下面的代碼示例所示:

          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和類的靜態(tài)初始化器(它在java.lang.reflect.*中沒有出現(xiàn))的包裝器。這與AspectJ JoinPoint.StaticPart.getSignature()抽象化相似。

            下面是當前定義的注釋及其含義。

            注釋

            公開

            備注

            @CalleeMethod

            被調(diào)用者方法(方法、構(gòu)造函數(shù)、靜態(tài)初始化器)

            ?

            @CallerMethod

            調(diào)用者方法(方法、構(gòu)造函數(shù)、靜態(tài)初始化器)

            ?

            @Callee

            被調(diào)用者實例

            濾除靜態(tài)成員調(diào)用。用作instance-of型過濾器:被調(diào)用者類型必須是所注釋的參數(shù)類型的實例。

            @Caller

            調(diào)用者實例

            濾除來自靜態(tài)成員的調(diào)用。用作instance-of型過濾器:調(diào)用者類型必須是所注釋的參數(shù)類型的實例。

            @Arguments

            調(diào)用參數(shù)

            ?

            為了支持instead-of,并能夠決定是否沿著截取鏈前進(就像在AOP中通過JoinPoint.proceed()概念實現(xiàn)),我們引入了jrockit.ext.weaving.InvocationContext構(gòu)造,如下所示:

          import jrockit.ext.weaving.*;
          
          public class InsteadOfAction {
          
            public Object instead(
                            InvocationContext jp,
                            @CalleeMethod Method callee) {
              return jp.proceed();
            }
          }
          

          API的細節(jié):動作實例和動作類型

            正如前面代碼示例中所示,動作方法可以是靜態(tài)的,也可以不是。如果動作方法不是靜態(tài)的,那么就必須傳遞一個動作實例,JVM在這個實例上調(diào)用動作方法。

            其語法風格與Java開發(fā)人員使用java.lang.reflect.Method.invoke(null/*static method*/, .../*args*/)對方法進行反射式調(diào)用一樣。但是,利用JVM的AOP支持,底層的動作調(diào)用根本不涉及任何反射。

            允許用戶控制動作實例,就會產(chǎn)生有趣的用例。例如,可以實現(xiàn)一個簡單的委托模式,在運行時用另一個實現(xiàn)替換整個動作實例,而不涉及JVM的內(nèi)部組件。

            注意,這將有助于(按照規(guī)定)實現(xiàn)AOP方面實例化模型,比如issingleton()、pertarget()、perthis()、percflow()等等,同時不會將JVM API限制在某些預定義的語義上。

            在將預定注冊到編織器實例之前,賦予它一個類型作為建議類型:before、instead-of、after-returning或after-throwing。

            可以編寫下面這樣的代碼來創(chuàng)建預定:

          // 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
            );
          
          MethodSubscription ms = new MethodSubscription(
              .../* where to match*/,
              InsertionType.BEFORE,
              staticActionMethod
          );
          
          w.addSubscription(ms);

            該代碼示例假設用戶使用靜態(tài)動作方法實現(xiàn)。也可以使用實例方法編寫這個示例,在這種情況下,應該傳遞給MethodSubscription一個包含類實例。

          // Use of an action instance to refer to the
          // non static action method "simpleAction()"
          Method actionMethod =
            SimpleAction.class.getDeclaredMethod(
                "simpleAction",
                new Class[0]// no arguments
            );
          // Instantiate the action instance
          SimpleAction actionInstance = new SimpleAction();
          MethodSubscription ms2 = new MethodSubscription(
              ...,// where to match, explained below
              InsertionType.BEFORE,
              actionMethod,
              actionInstance
          );
          
          w.addSubscription(ms2);
          

            諸如within()和withincode()類型模式之類的AOP語義也通過該API的變體實現(xiàn)。

          API細節(jié):預定

            如前面的代碼示例所示,預定API依賴于java.lang.reflect.*對象模型和一些簡單的抽象化(比如jrockit.ext.weaving.WMethod)來合并方法、構(gòu)造函數(shù)和類的靜態(tài)初始化器處理。

            new MethodSubscription(...)調(diào)用的第一個參數(shù)必須是jrockit.ext.weaving.Filter實例,這個實例具有幾個具體實現(xiàn)以便匹配方法、字段等等。

            jrockit.ext.weaving.MethodFilter實例用作定義,JVM編織器實現(xiàn)根據(jù)它進行聯(lián)結(jié)點陰影匹配(shadow matching)。jrockit.ext.weaving.MethodFilter允許根據(jù)以下各項進行過濾(還提供額外的結(jié)構(gòu)支持within()/withincode()語義):

          • 方法修飾符(比如使用java.lang.reflect.Modifier時的int)。
          • Class<? extends java.lang.annotation.Annotation>,匹配方法運行時可見性注釋。
          • jrockit.ext.weaving.ClassFilter實例,匹配聲明類型。
          • jrockit.ext.weaving.StringFilter實例,匹配方法名。
          • jrockit.ext.weaving.ClassFilter實例,匹配方法返回類型。
          • jrockit.ext.weaving.UserDefinedFilter實例,用于實現(xiàn)更精細的匹配邏輯。

            使用jrockit.ext.weaving.UserDefinedFilter回調(diào)機制來實現(xiàn)更高級的匹配方式(與Spring AOPorg.springframework.aop.MethodMatcher和org.springframework.aop.ClassFilter相似)。

            所有這些結(jié)構(gòu)都是可選的,如果遇到null,就表示“任意匹配”。

            jrockit.ext.weaving.ClassFilter提供一種類似的方式:

          • Class<? extends java.lang.annotation.Annotation>,匹配類運行時可見性注釋。
          • Class,匹配類類型。
          • boolean值,表示是否匹配子類型。

            因此,以下代碼匹配所有名稱以“bar”開頭的方法調(diào)用。注意,在這個非常簡單的例子中傳遞了好幾個null值:

          StringFilter sf =
            new StringFilter("bar", STARTSWITH);
          
          MethodFilter mf =
            new MethodFilter(0, null, null, sf, null, null);
          
          MethodSubscription ms = new MethodSubscription(
              mf,
              InsertionType.BEFORE,
              staticActionMethod
          );
          
          w.addSubscription(ms);
          

            作為更現(xiàn)實的例子,以下代碼匹配所有三個EJB業(yè)務方法:

          // Prepare the pointcut to match
          // @Stateless annotated classes business methods
          MethodFilter ejbBizMethods = new MethodFilter(
              PUBLIC_NONSTATIC,
              // Method annotation does not matter
              null,
              new ClassFilter(
                  // Declaring class, the java.lang.Class
                  // for the EJB we are currently manipulating
                  ejbClass,
                  // no subtypes matching
                  false,
                  // class annotation
                  Stateless.class
              ),
              // EJB methods matching is handled
              // in a UserDefinedFilter below instead
              null,
              // return type does not matter
              null,
              // custom Filter callback
              new UserDefinedFilter() {
                  public boolean match(
                                 MethodFilter methodFilter,
                                 WMember member,
                                 WMethod within) {
                      return !isEjbLifeCycleMethod(member);
                  }
              }
          );
          

          好處

            使用JVM編織而不是字節(jié)碼測試有幾個好處。從較高的層面來看,編織作為JVM功能的自然擴展出現(xiàn),因此在許多方面它不那么具有侵入性,并且為性能、可伸縮性和可用性各方面帶來了許多好處。

            關于字節(jié)碼編織的問題(尤其是在加載時編織的情況下)的詳細討論,請參考本系列的第1部分。以下好處解決了所有這些問題。

          • 不使用字節(jié)碼測試,增強了可伸縮性

            字節(jié)碼沒有被修改。在JVM內(nèi)部組件中,仍然采用從字節(jié)碼到可執(zhí)行代碼的常規(guī)編譯管道。使用字節(jié)碼測試時,需要分析字節(jié)碼指令并用某些中間結(jié)構(gòu)來表示它們,這樣才能在測試框架(AOP編織器或基于字節(jié)碼測試的產(chǎn)品)中操縱它們;而使用JVM編織不需要這么做。

            編織器變得無所不在了。即使用戶希望在啟動時注冊預定,這也不再是必須的。因為根本不需要分析字節(jié)碼指令來尋找要截取的聯(lián)結(jié)點,所以大大減少了應用程序的啟動時間。這也提供了開發(fā)真正動態(tài)的系統(tǒng)的機會——動態(tài)意味著可以在任何時候部署方面和解除方面部署,而又不會由此引起額外的開銷或復雜性。

          • 不使用冗余的類型信息記錄,降低了內(nèi)存耗用并且提高了可伸縮性

            因為不再進行字節(jié)碼測試,因此與對象模型雙重記錄問題相關的問題就不會出現(xiàn)了。預定API依賴于java.lang.reflect.*模型,而這個模型已經(jīng)以類似的方式向Java開發(fā)人員提供了此信息。

          • 多個代理可以保持一致

            因為所編織的類的字節(jié)碼沒有經(jīng)過修改,所以不會因為兩個不同的代理以不兼容的方式修改字節(jié)碼(相互隱藏原始程序的屬性),而造成沖突。預定的注冊次序起到了優(yōu)先權規(guī)則的作用。注意,如果類是可序列化的,那么不會為了在運行時執(zhí)行所編織的建議而向其添加隱藏結(jié)構(gòu),所以常規(guī)的序列化將得到充分支持。而字節(jié)碼測試技術通常需要確定序列化能力是否有所保留(例如,serialVersionUID字段的處理)。

          • 支持截取反射式調(diào)用

            通過使用JVM級方法調(diào)度,所有反射式調(diào)用(方法調(diào)用或者get或set字段)都可以被匹配,就像它們是常規(guī)調(diào)用,而且所有注冊的動作都將被觸發(fā)一樣。這不需要任何額外的開銷,也不涉及特定于實現(xiàn)的細節(jié)和復雜性。

          未來的發(fā)展方向

            盡管JVM編織很有幫助,而且解決了與字節(jié)碼測試技術相關的可伸縮性和可用性問題,但是仍然必須解決一些缺陷才能使其完美地實現(xiàn)用例,這可能需要采用一些補充方法。

            一些基于字節(jié)碼測試的產(chǎn)品使用了細粒度更改,當前的JVM AOP API還無法實現(xiàn)這一特性。某些用例處理同步塊,因此不同的鎖定機制(如:分布式鎖定)可以透明地注入常規(guī)的應用程序。這樣的細粒度動作常常要求對同步塊進行有條件執(zhí)行,甚至完全刪除同步塊,并使用某個專用鎖定API調(diào)用來替換它。可以在JVM中解決這樣的特定需求,但是實際上不可能找到一個對每種用例都有效的高效解決方案。還有必要提醒一下的是,目前領先的AOP框架還不能將同步塊公開為聯(lián)結(jié)點。

            在JVM級別上,無法輕松地實現(xiàn)AspectJ定義的某些細粒度語義。例如,AspectJ支持預初始化初始化構(gòu)造函數(shù)執(zhí)行切點。構(gòu)造函數(shù)執(zhí)行切點挑選出源代碼中出現(xiàn)的構(gòu)造函數(shù),初始化切點挑選出獲得已初始化實例的所有構(gòu)造函數(shù)執(zhí)行,包括this(...)構(gòu)造函數(shù)委托。JVM難以把握這兩者的差異。更具侵入性的代碼內(nèi)聯(lián)策略可能會出現(xiàn)在哪些地方實際上也可能取決于編譯器。

            隨著字節(jié)碼測試逐漸流行起來,新的JVM API的引入肯定會遇到挑戰(zhàn)。如果要開發(fā)一種同時適應兩種JVM(支持新API的JVM,比如JRockit,以及不支持新API的JVM)的產(chǎn)品,那么成本會相當高。這個領域中的規(guī)范(如:JSR)可能有助于克服這種困難。

          結(jié)束語

            字節(jié)碼測試技術目前已經(jīng)在不同領域的Java平臺上得到廣泛使用,從面向方面軟件開發(fā)到更特定于應用的解決方案(如:應用程序監(jiān)控、持久性或分布式計算)。隨著字節(jié)碼測試的可用性和透明性的提高,加載時編織和部署時測試將會流行起來。

            遺憾的是,這種技術沒有為可伸縮性和可用性需求提供適當?shù)闹С帧L貏e是隨著這種技術的應用越來越廣泛,以及對來自不同產(chǎn)品的不同測試代理的混合使用,這個問題會越來越嚴重。JVM編織和JVM對AOP的支持(比如在JRockit中所實現(xiàn)的)是解決這個問題的自然方法,可以促進革新和技術發(fā)展。JRockit團隊所提出的Java API將JVM方法調(diào)度內(nèi)部組件與用戶定義的動作聯(lián)系起來,僅依賴于java.lang.reflect API的預定優(yōu)雅地填補了以前的鴻溝,并解決了主要的可伸縮性和可用性問題。

            這種新的API要想獲得廣泛采用,需要對它進行認真的評估,并將它應用于真實的用例,比如AOP或者大型應用程序的運行時自適應。

          posted on 2006-04-01 11:20 aa611 閱讀(105) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 宁波市| 绥芬河市| 南郑县| 柞水县| 西吉县| 长顺县| 锡林浩特市| 泊头市| 龙海市| 大连市| 东明县| 稻城县| 阿巴嘎旗| 鹿邑县| 宣汉县| 葵青区| 磴口县| 宿迁市| 安平县| 池州市| 抚松县| 宜兰市| 阆中市| 仁布县| 东莞市| 南昌市| 玉田县| 安西县| 连江县| 大荔县| 禹城市| 江华| 平和县| 洛扎县| 沁水县| 东阳市| 林周县| 吴忠市| 滨海县| 公安县| 思南县|