Sealyu

          --- 博客已遷移至: http://www.sealyu.com/blog

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            618 隨筆 :: 87 文章 :: 225 評(píng)論 :: 0 Trackbacks

          BCEL  介紹:

          Byte Code Engineering Library (BCEL),這是Apache Software Foundation 的Jakarta 項(xiàng)目的一部分。
          正在裝載數(shù)據(jù)……
          BCEL 是 Java classworking 最廣泛使用的一種框架,它可以讓您深入 JVM 匯編語言進(jìn)行類操作的細(xì)節(jié)。BCEL與Javassist 有不同的處理字節(jié)碼方法,BCEL在實(shí)際的JVM 指令層次上進(jìn)行操作(BCEL擁有豐富的JVM 指令級(jí)支持)而Javassist 所強(qiáng)調(diào)的源代碼級(jí)別的工作。

          http://sourceforge.net/projects/bcel

          Javassist:使字節(jié)碼工程變得簡單(Zz) :

          javassist 是一個(gè)執(zhí)行字節(jié)碼操作的強(qiáng)而有力的驅(qū)動(dòng)代碼庫。它允許開發(fā)者自由的在一個(gè)已經(jīng)編譯好的類中添加新的方法,或者是修改已有的方法。但是,和其他的類似庫不同 的是,Javassist并不要求開發(fā)者對(duì)字節(jié)碼方面具有多么深入的了解,同樣的,它也允許開發(fā)者忽略被修改的類本身的細(xì)節(jié)和結(jié)構(gòu)。

          字節(jié)碼驅(qū)動(dòng)通常被用來執(zhí)行對(duì)于已經(jīng)編譯好的類的修改,或者由程序自動(dòng)創(chuàng)建執(zhí)行類等等等等相關(guān)方面的操作。這就要求字節(jié)碼引擎具備無論是在運(yùn)行時(shí)或是編譯時(shí)都能修改程序的能力。當(dāng)下有些技術(shù)便是使用字節(jié)碼來強(qiáng)化已經(jīng)存在的Java類的,也有的則是使用它來使用或者產(chǎn)生一些由系統(tǒng)在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的類。舉例而言,JDO1.0規(guī)范就使用了字節(jié)碼技術(shù)對(duì)數(shù)據(jù)庫中 的表進(jìn)行處理和預(yù)編譯,并進(jìn)而包裝成Java類。特別是在面向?qū)ο篁?qū)動(dòng)的系統(tǒng)開發(fā)中,相當(dāng)多的框架體系使用字節(jié)碼以使我們更好的獲得程序的范型性和動(dòng)態(tài) 性。而某些EJB容器,比如JBOSS項(xiàng)目,則通過在運(yùn)行中動(dòng)態(tài)的創(chuàng)建和加載EJB,從而戲劇性的縮短了部署EJB的周期。這項(xiàng)技術(shù)是如此的引人入勝,以至于在JDK中也有了標(biāo)準(zhǔn)的java.lang.reflect.Proxy類來執(zhí)行相關(guān)的操作。


          但 是,盡管如此,編寫字節(jié)碼對(duì)于框架程序開發(fā)者們而言,卻是一個(gè)相當(dāng)不受歡迎的繁重任務(wù)。學(xué)習(xí)和使用字節(jié)碼在某種程度上就如同使用匯編語言。這使得于大多數(shù) 開發(fā)者而言,盡管在程序上可以獲得相當(dāng)多的好處,可攀登它所需要的難度則足以冷卻這份熱情。不僅如此,在程序中使用字節(jié)碼操作也大大的降低了程序的可讀性 和可維護(hù)性。


          這是一塊很好的奶油面包,但是我們卻只能隔著櫥窗流口水…難道我們只能如此了嗎?


          所幸的是,我們還有Javassist。Javassist是一個(gè)可以執(zhí)行字節(jié)碼操作的函數(shù)庫,可是盡管如此,它卻是簡單而便與理解的。他允許開發(fā)者對(duì)自己的程序自由的執(zhí)行字節(jié)碼層的操作,當(dāng)然了,你并不需要對(duì)字節(jié)碼有多深的了解,或者,你根本就不需要了解。


          API Parallel to the Reflection API


          Javassist 的最外層的API和JAVA的反射包中的API頗為類似。它使你可以在裝入ClassLoder之前,方便的查看類的結(jié)構(gòu)。它主要由 CtClass,,CtMethod,,以及CtField幾個(gè)類組成。用以執(zhí)行和JDK反射API中java.lang.Class,, java.lang.reflect.Method,, java.lang.reflect.Method .Field相同的操作。這些類可以使你在目標(biāo)類被加載前,輕松的獲得它的結(jié)構(gòu),函數(shù),以及屬性。此外,不僅僅是在功能上,甚至在結(jié)構(gòu)上,這些類的執(zhí)行函 數(shù)也和反射的API大體相同。比如getName,getSuperclass,getMethods,,getSignature,等等。如果你對(duì) JAVA的反射機(jī)制有所了解的話,使用Javassist的這一層將會(huì)是輕松而快樂的。


          接下來我們將給出一個(gè)使用Javassist來讀取org.geometry.Point.class的相關(guān)信息的例子(當(dāng)然了,千萬不要忘記引入javassist.*包):


          1. ClassPool pool = ClassPool.getDefault();


          2. CtClass pt = pool.get("org.geometry.Point");


          3. System.out.println(pt.getSuperclass().getName());


          其 中,ClassPool是CtClass 的創(chuàng)建工廠。它在class path中查找CtClass的位置,并為每一個(gè)分析請(qǐng)求創(chuàng)建一個(gè)CtClass實(shí)例。而“getSuperclass().getName()”則展示 出org.geometry.Point.class所繼承的父類的名字。


          但是,和反射的API不盡相同的是,Javassist 并不提供構(gòu)造的能力,換句話說,我們并不能就此得到一個(gè) org.geometry.Point.class類的實(shí)例。另一方面,在該類沒有實(shí)例化前,Javassist也不提供對(duì)目標(biāo)類的函數(shù)的調(diào)用接口和獲取 屬性的值的方法。在分析階段,它僅僅提供對(duì)目標(biāo)類的類定義修改,而這點(diǎn),卻是反射API所無法做到的。


          舉例如下:


          4. pt.setSuperclass(pool.get("Figure"));


          這樣做將修改目標(biāo)類和其父類之間的關(guān)系。我們將使org.geometry.Point.clas改繼承自Figure類。當(dāng)然了,就一致性而言,必須確保Figure類和原始的父類之間的兼容性。


          而往目標(biāo)類中新增一個(gè)新的方法則更加的簡單了。首先我們來看字節(jié)碼是如何形成的:


          5. CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", pt);


          6. pt.addMethod(m);


          CtMethod類的讓我們要新增一個(gè)方法只需要寫一段小小的函數(shù)。這可是一個(gè)天大的好消息,開發(fā)者們?cè)僖膊挥脼榱藢?shí)現(xiàn)這么一個(gè)小小的操作而寫一大段的虛擬機(jī)指令序列了。Javassist將使用一個(gè)它自帶的編譯器來幫我們完成這一切。


          最后,千萬別忘了指示Javassist把已經(jīng)寫好的字節(jié)碼存入到你的目標(biāo)類里:


          7. pt.writeFile();


          writeFile方法可以幫我們把修改好了的定義寫到目標(biāo)類的.class文件里。當(dāng)然了,我們甚至可以在該目標(biāo)類加載的時(shí)候完成這一切,Javassist可以很好的和ClassLoader協(xié)同工作,我們不久就將看到這一點(diǎn)。


          Javassist 并不是第一套用以完成從代碼到字節(jié)碼的翻譯的函數(shù)庫。Jakarta的BCEL也是一個(gè)比較知名的字節(jié)碼引擎工具。但是,你卻無法使用BCEL來完成代碼 級(jí)別的字符碼操作。如果你需要在一個(gè)已經(jīng)編譯好的類中添加一個(gè)新的方法,假如你用的是BCEL的話,你只能定義一段由那么一大串字符碼所構(gòu)成的指令序列。 正如上文所說,這并不是我們所希望看到的。因此,就此方面而言,Javassis使用代碼的形式來插入新的方法實(shí)在是一大福音。


          Instrumenting a Method Body


          和 方法的新增一樣,對(duì)于一個(gè)類的方法的其他操作也是定義在代碼層上的。換而言之,盡管這些步驟是必須的,開發(fā)者們也同樣無須直接對(duì)虛擬機(jī)的指令序列進(jìn)行操作 和修改,Javassis將自動(dòng)的完成這些操作。當(dāng)然了,如果開發(fā)者認(rèn)為自己有必要對(duì)這些步驟進(jìn)行管理和監(jiān)控,或者希望由自己來管理這些操作的 話,Javassist同樣提供了更加底層的API來實(shí)現(xiàn),不過我們?cè)谶@篇文章中將不會(huì)就此話題再做深入探討。恩,盡管從結(jié)構(gòu)而言,它和BCEL的字節(jié)碼 層API差不多。


          設(shè)計(jì)Javassist對(duì)目標(biāo)類的子函數(shù)體的操作API的設(shè)想立足與Aspect-Oriented Programming(AOP)思想。Javassist允許把具有耦合關(guān)系的語句作為一個(gè)整體,它允許在一個(gè)插入語句中調(diào)用或獲取其他函數(shù)或者及屬性 值。它將自動(dòng)的對(duì)這些語句進(jìn)行優(yōu)先級(jí)分解并執(zhí)行嵌套操作。


          如下例所示,清單1首先包含了一個(gè)CtMethod,它主要針對(duì) Screen類的draw方法。然后,我們定義一個(gè)Point類,該類有一個(gè) move操作,用來實(shí)現(xiàn)該P(yáng)oint的移動(dòng)。當(dāng)然了,在移動(dòng)前,我們希望可以通過draw方法得到該point目前的位置,那么,我們需要對(duì)該move方 法加增如下的定義:


          { System.out.println("move"); $_ = $proceed($$); }


          這樣,在執(zhí)行move之前,我們就可以打印出它的位置了。請(qǐng)注意這里的調(diào)用語句,它是如下格式的:


          $_ = $proceed($$);


          這樣我們就將使用原CtMethod類中的process()對(duì)該point的位置進(jìn)行追蹤了。


          基 與如上情況,CtMethod的關(guān)于methord的操作其實(shí)被劃分成了如下步驟,首先,CtMethod的methord將掃描插入語句(代碼)本身。 一旦發(fā)現(xiàn)了子函數(shù),則創(chuàng)建一個(gè)ExprEditor實(shí)例來分析并執(zhí)行這個(gè)子函數(shù)的操作。這個(gè)操作將在整個(gè)插入語句執(zhí)行之前完成。而假如這個(gè)實(shí)例存在某個(gè) static的屬性,那么methord將率先檢測對(duì)插入語句進(jìn)行檢測。然后,在執(zhí)行插入到目標(biāo)類---如上例的point類---之前,該 static屬性將自動(dòng)的替換插入語句(代碼)中所有的相關(guān)的部分。不過,值得注意的是,以上的替換操作,將在Javassist把插入語句(代碼)轉(zhuǎn)變 為字節(jié)碼之后完成。


          Special Variables


          在替換的語句(代碼)中,我們也有可能需要用到一些 特殊變量來完成對(duì)某個(gè)子函數(shù)的調(diào)用,而這個(gè)時(shí)候我們就需要使用關(guān)鍵字“$”了。在 Javassist中,“$”用來申明此后的某個(gè)詞為特殊參數(shù),而“$_”則用來申明此后的某個(gè)詞為函數(shù)的回傳值。每一個(gè)特殊參數(shù)在被調(diào)用時(shí)應(yīng)該是這個(gè)樣 子的“$1,$2,$3…”但是,特別的,目標(biāo)類本身在被調(diào)用時(shí),則被表示為“$0”。這種使用格式讓開發(fā)者在填寫使用子函數(shù)的參數(shù)時(shí)輕松了許多。比如如 下的例子:


          { System.out.println("move"); $_ = $proceed($1, 0); }


          請(qǐng)注意,該子函數(shù)的第2個(gè)參數(shù)為0。


          另 外一個(gè)特殊類型則是$arg,它實(shí)際上是一個(gè)容納了函數(shù)所有調(diào)用參數(shù)的Object隊(duì)列。當(dāng)Javassist在掃描該$arg時(shí),如果發(fā)現(xiàn)某一個(gè)參數(shù)為 JAVA的基本類型,則它將自動(dòng)的對(duì)該參數(shù)進(jìn)行包裝,并放入隊(duì)列。比如,當(dāng)它發(fā)現(xiàn)某一個(gè)參數(shù)為int類型時(shí),它將使用 java.lang.integer 類來包裝這個(gè)int參數(shù),并存入?yún)?shù)隊(duì)列。和Java的反射包:java.lang.reflect.Methord類中的invoke方法相比,$ args明顯要省事的多。


          Javassist也同樣允許開發(fā)者在某個(gè)函數(shù)的頭,或者某個(gè)函數(shù)的尾上插入某段語句(代碼)。比如,它有一個(gè)insertBefore方法用以在某函數(shù)的調(diào)用前執(zhí)行某個(gè)操作,它的使用大致是這個(gè)樣子的:


          1. ClassPool pool = ClassPool.getDefault();
          2. CtClass cc = pool.get("Screen");
          3. CtMethod cm = cc.getDeclaredMethod("draw", new CtClass[0]);
          4. cm.insertBefore("{ System.out.println($1); System.out.println($2); }");
          5. cc.writeFile();


          以上例子允許我們?cè)赿raw函數(shù)調(diào)用之前執(zhí)行打印操作---把傳遞給draw的兩個(gè)參數(shù)打印出來。


          同樣的,我們也可以使用關(guān)鍵字$對(duì)某一個(gè)函數(shù)進(jìn)行修改或者是包裝,下面就


          1. CtClass cc = sloader.get("Point");
          2. CtMethod m1 = cc.getDeclaredMethod("move");
          3. CtMethod m2 = CtNewMethod.copy(m1, cc, null);
          4. m1.setName(m1.getName() + "_orig");
          5. m2.setBody("{ System.out.println("call"); return $proceed($$);
          }", "this", m1.getName());
          6. cc.addMethod(m2);
          7. cc.writeFile();


          以 上代碼的前四行不難理解,Javassist首先對(duì)Point中的move方法做了個(gè)拷貝,并創(chuàng)建了一個(gè)新的函數(shù)。然后,它把存在與Point類中的原 move方法更名為“_orig”。接下來,讓我們關(guān)注一下程序第五行中的幾個(gè)參數(shù):第一個(gè)參數(shù)指示該函數(shù)的在執(zhí)行的最初部分需要先打印一段信息,然后執(zhí) 行子函數(shù)proceed()并返回結(jié)果,這個(gè)和move方法差不多,很好理解。第二個(gè)參數(shù)則只是申明該子函數(shù)所在的類的位置。這里為this即為 Point類本身。第三個(gè)參數(shù),也就是“m1.getName()”則定義了這個(gè)新函數(shù)的名字。


          Javassist也同樣具有其他的操作和類來幫助你實(shí)現(xiàn)諸如修改某一個(gè)屬性的值,改變函數(shù)的回值,并在某個(gè)函數(shù)的執(zhí)行后補(bǔ)上其他操作的功能。您可以瀏覽www.javassist.org以獲得相關(guān)的信息

           

          使用Javassist對(duì).class文件進(jìn)行修改(Zz) :

          最 近重新再看<Inside JVM>,對(duì)JAVA編譯成的字節(jié)碼結(jié)構(gòu)很感興趣,希望找個(gè)工具能夠?qū)?class文件進(jìn)行的解析和查看。沒找到,倒發(fā)現(xiàn)javaassist可以 對(duì)字節(jié)碼進(jìn)行操作和修改。此工具是JBOSS項(xiàng)目的一部分,JBOSS實(shí)現(xiàn)AOP的基礎(chǔ)。呵呵,開眼界了,原來我們可以直接對(duì)字節(jié)碼文件進(jìn)行修改,哪怕不 知道源文件(跟反編譯完全不同)。一個(gè)簡單例子:

          import javassist.*;
          class Hello {
              public void say() {
                  System.out.println("Hello");
              }
          }

          public class Test {
              public static void main(String[] args) throws Exception {
                  ClassPool cp = ClassPool.getDefault();
                  CtClass cc = cp.get("Hello");
                  CtMethod m = cc.getDeclaredMethod("say");
                  m.setBody("{System.out.println(""shit"");}");
                  m.insertBefore("System.out.println(""fuck"");");
                  Class c = cc.toClass();
                  Hello h = (Hello)c.newInstance();
                  h.say();
              }
          }

          編譯運(yùn)行此文件,輸出:

          fuck

          shit

          我們?cè)?/p>

           CtMethod m = cc.getDeclaredMethod("say");
            m.setBody("{System.out.println(""shit"");}");

            m.insertBefore("System.out.println(""fuck"");");

          修改了say()方法,改成了

          System.out.println("fuck");

          System.out.println("shit");

          這里的ClassPool是CtClass的容器,它讀取class文件,并根據(jù)要求保存CtClass的結(jié)構(gòu)以便日后使用,默認(rèn)狀態(tài)下是從當(dāng)前的類裝載器獲得,當(dāng)然你可以指定:

          pool.insertClassPath("/usr/local/javalib");

          當(dāng)然,不僅僅是修改方法,你還可以新建一個(gè)class,利用makeClass()方法,如:

          ClassPool pool = ClassPool.getDefault();
          CtClass cc = pool.makeClass("Point");

          還可以新增方法,下面是sample里的一個(gè)例子,同樣的:

          package sample;

          import javassist.*;
          import java.lang.reflect.*;

          /*
             A very simple sample program

             This program overwrites sample/Test.class (the class file of this
             class itself) for adding a method g().  If the method g() is not
             defined in class Test, then this program adds a copy of
             f() to the class Test with name g().  Otherwise, this program does
             not modify sample/Test.class at all.

             To see the modified class definition, execute:

             % javap sample.Test

             after running this program.
          */
          public class Test {
              public int f(int i) {
               i++;
              return i;
              }

              public static void main(String[] args) throws Exception {
           ClassPool pool = ClassPool.getDefault();

           CtClass cc = pool.get("sample.Test");
           Test test=new Test();
           Class c=test.getClass();
           Method []method=c.getDeclaredMethods();
           for(int i=0;i<method.length;i++){
            System.out.println(method[i]);
           }
           try {
               cc.getDeclaredMethod("g");
               System.out.println("g() is already defined in sample.Test.");
           }
           catch (NotFoundException e) {
               /* getDeclaredMethod() throws an exception if g()
                * is not defined in sample.Test.
                */
               CtMethod fMethod = cc.getDeclaredMethod("f");
               CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);
               cc.addMethod(gMethod);
               cc.writeFile(); // update the class file
               System.out.println("g() was added.");
           }
              }
          }
          第一次運(yùn)行時(shí),因?yàn)門est里并沒有g(shù)()方法,所以執(zhí)行

           CtMethod fMethod = cc.getDeclaredMethod("f");
               CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);  //把f方法復(fù)制給g
               cc.addMethod(gMethod);
               cc.writeFile(); //更新class文件

               System.out.println("g() was added.");
          打印:g() was added

          第2次運(yùn)行時(shí),因?yàn)橐陨喜襟E已經(jīng)在class文件中增加了一個(gè)g方法,所以

           System.out.println("g() is already defined in sample.Test.");
          打印:g() is already defined in sample.Test

          Javassist不僅能修改你自己的class文件,而且可以同樣修改JDK自帶的類庫(廢話,類庫也是人寫的^_^)具體請(qǐng)看它的tutorial。


          Javassist 官方網(wǎng)站: http://www.csg.is.titech.ac.jp/~chiba/javassist/

          posted on 2009-03-16 15:43 seal 閱讀(490) 評(píng)論(0)  編輯  收藏 所屬分類: Java基礎(chǔ)
          主站蜘蛛池模板: 嘉义县| 红原县| 永仁县| 神池县| 灵寿县| 韶关市| 拉孜县| 鄂托克旗| 九龙坡区| 皋兰县| 河北省| 深州市| 梅河口市| 永春县| 田林县| 马尔康县| 清镇市| 鲁山县| 肇州县| 大同县| 准格尔旗| 嘉善县| 米脂县| 泌阳县| 区。| 栾川县| 新巴尔虎左旗| 石台县| 安宁市| 厦门市| 那曲县| 阿尔山市| 安顺市| 根河市| 宜州市| 晴隆县| 吴桥县| 武义县| 垦利县| 麻城市| 仲巴县|