Javassist 官方文檔 隨手筆記

Javassist.CtClass

Javassist.CtClass是類文件的抽象表示。CtClass(編譯時類)對象是用於處理類文件的句柄。以下程序是一個非常簡單的示例:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();

writeFile()
將CtClass對象轉換爲類文件,並將其寫入本地磁盤。Javassist還提供了一種直接獲取修改後的字節碼的方法。要獲取字節碼,請調用Bytecode():

byte [] b = cc.toBytecode();

您也可以直接加載CtClass:

clazz類= cc.toClass();

要從頭開始定義新類,必須在ClassPool上調用makeClass()。

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

如果通過writeFile(),toClass()或toBytecode()將CtClass對象轉換爲類文件,則Javassist會凍結該CtClass對象。不允許對該CtClass對象做進一步修改。這是爲了警告開發人員,因爲JVM不允許重新加載類,因此當他們嘗試修改已加載的類文件時。
凍結的CtClass可以解凍,以便允許修改類定義。例如,

CtClasss cc = ...;
    :
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // OK since the class is not frozen.

調用defrost()之後,可以再次修改CtClass對象。

如果將ClassPool.doPruning設置爲true,則Javassist凍結該對象時,Javassist會修剪CtClass對象中包含的數據結構。爲了減少內存消耗,修剪會丟棄該對象中不必要的屬性(attribute_info結構)。例如,丟棄Code_attribute結構(方法主體)。因此,修剪CtClass對象後,除方法名稱,簽名和註釋外,方法的字節碼不可訪問。修剪後的CtClass對象不能再次解凍。ClassPool.doPruning的默認值爲false。

CtClasss cc = ...;
cc.stopPruning(true);
    :
cc.writeFile();                             // convert to a class file.
// cc is not pruned.

Class search path

靜態方法ClassPool.getDefault()返回的默認ClassPool搜索與基礎JVM(Java虛擬機)具有的路徑相同。如果程序正在Web應用程序服務器(例如JBoss和Tomcat)上運行,則ClassPool對象可能無法找到用戶類,因爲這樣的Web應用程序服務器使用多個類加載器以及系統類加載器。在這種情況下,必須將其他類路徑註冊到ClassPool。假設池引用了一個ClassPool對象:

pool.insertClassPath(new ClassClassPath(this.getClass()));

您可以將目錄名稱註冊爲類搜索路徑。例如,以下代碼將目錄/ usr / local / javalib添加到搜索路徑:

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

用戶可以添加的搜索路徑不僅是目錄,而且是URL:

ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

#ClassPool

ClassPool對象是CtClass對象的容器。創建CtClass對象後,它將永遠記錄在ClassPool中。這是因爲編譯器在編譯引用由該CtClass表示的類的源代碼時可能需要在以後訪問CtClass對象。

如果CtClass對象的數量變得異常龐大,則此ClassPool規範可能會導致巨大的內存消耗(這很少發生,因爲Javassist嘗試以各種方式減少內存消耗)。爲避免此問題,您可以從ClassPool中顯式刪除不必要的CtClass對象。如果在CtClass對象上調用detach(),則將從ClassPool中刪除該CtClass對象。例如,

CtClass cc = ... ;
cc.writeFile();
cc.detach();

在調用detach()之後,您不得在該CtClass對象上調用任何方法。但是,您可以在ClassPool上調用get()來製作一個表示相同類的CtClass的新實例。如果調用get(),則ClassPool將再次讀取類文件,並新創建一個CtClass對象,該對象由get()返回。

Introspection and customization

傳遞給方法insertBefore(),insertAfter(),addCatch()和insertAt()的String對象由Javassist中包含的編譯器進行編譯。由於編譯器支持語言擴展,因此以$開頭的幾個標識符具有特殊含義:

符號 原文 翻譯
$0, $1, $2, … this and actual parameters 參數
$args An array of parameters. The type of $args is Object[]. 參數數組。$ args的類型爲Object []。
$$ All actual parameters.For example, m($$) is equivalent to m($1,$2,…) 所有實際參數。例如,m($m)等效於m( 1,$ 2,…)
$cflow(…) cflow variable cflow變量
$r The result type. It is used in a cast expression. 結果類型。在強制轉換表達式中使用。
$w The wrapper type. It is used in a cast expression. 包裝器類型。在強制轉換表達式中使用。
$_ The resulting value 返回值
$sig An array of java.lang.Class objects representing the formal parameter types. 表示形式參數類型的java.lang.Class對象的數組。
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.

$0, $1, $2, …

傳遞給目標方法的參數可以用$ 1,$ 2,…而不是原始參數名訪問。$ 1代表第一個參數,$ 2代表第二個參數,依此類推。這些變量的類型與參數類型相同。$ 0等效於此。如果該方法是靜態的,則$ 0不可用。

class Point {
    int x, y;
    void move(int dx, int dy) {
        { System.out.println(dx); System.out.println(dy); }
        x += dx; y += dy;
    }
}

$ 1和$ 2分別替換爲dx和dy。$ 1,$ 2,$ 3 …是可更新的。如果將新值分配給這些變量之一,則該變量表示的參數值也會更新。

變量$ args表示所有參數的數組。
該變量的類型是Object類的數組。
如果參數類型是基本類型(例如int),則參數值將轉換爲包裝對象(例如java.lang.Integer)以存儲在$ args中。
因此,除非第一個參數的類型是原始類型,否則$ args [0]等效於$ 1。

$$

變量$$是用逗號分隔的所有參數的列表的縮寫。例如,如果方法move()的參數數量爲三個,則



move($$)
//二者相同
move($1, $2, $3)

$cflow

$ cflow表示“控制流”。該只讀變量將對特定方法的遞歸調用的深度返回。

假設下面顯示的方法由CtMethod對象cm表示:

int fact(int n) {
    if (n <= 1)
        return n;
    else
        return n * fact(n - 1);
}

要使用$cflow,首先聲明$cflow用於監視對方法fact()的調用:

CtMethod cm = ...;
cm.useCflow("fact");

useCflow()的參數是聲明的$cflow變量的標識符。任何有效的Java名稱都可以用作標識符。由於標識符也可以包含。(點),例如,“ my.Test.fact”是有效的標識符。

然後,$cflow(fact)表示對cm指定的方法進行遞歸調用的深度。第一次調用該方法時,$cflow(fact)的值爲0(零),而在該方法中遞歸調用時,該值爲1。例如,

cm.insertBefore("if ($cflow(fact) == 0)"
              + "    System.out.println(\"fact \" + $1);");

$r

$r表示方法的結果類型(返回類型)。它必須在強制轉換表達式中用作強制轉換類型。例如,這是一種典型用法:

Object result = ... ;
$_ = ($r)result;

$w

$w表示包裝器類型。它必須在強制轉換表達式中用作強制轉換類型。($w)從原始類型轉換爲相應的包裝器類型。以下代碼是一個示例:

Integer i = ($w)5;

$_

變量$_表示方法的結果值。該變量的類型是方法的結果類型(返回類型)的類型。如果結果類型爲void,則$ _的類型爲Object,$_的值爲null。

$sig

$sig的值是java.lang.Class對象的數組,它們按聲明順序表示形式參數類型。

$type

$type的值是一個java.lang.Class對象,表示結果值的形式類型。如果這是構造函數,則此變量引用Void.class。

$class

$class的值是一個java.lang.Class對象,表示在其中聲明已編輯方法的類。這代表$ 0的類型。

addCatch()

addCatch()將代碼片段插入方法主體,以便在方法主體引發異常並且控件返回到調用方時執行該代碼片段。在表示插入的代碼片段的源文本中,異常值用特殊變量$e引用。

CtMethod m = ...;
CtClass etype = ClassPool.getDefault().get("java.io.IOException");
m.addCatch("{ System.out.println($e); throw $e; }", etype);

//等同

 try {
    the original method body
}
catch (java.io.IOException e) {
    System.out.println(e);
    throw e;
}

添加屬性方法

添加方法

Javassist允許用戶從頭開始創建新的方法和構造函數。CtNewMethod和CtNewConstructor提供了幾種工廠方法,它們是用於創建CtMethod或CtConstructor對象的靜態方法。特別是,make()從給定的源文本創建CtMethod或CtConstructor對象。

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int xmove(int dx) { x += dx; }",
                 point);
point.addMethod(m);

傳遞給make()的源文本可以包含以$開頭的標識符,但set_Body()中的$_除外。如果還向make()提供了目標對象和目標方法名稱,則它也可以包含$proceed。例如,

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int ymove(int dy) { $proceed(0, dy); }",
                 point, "this", "move");
//等同
public int ymove(int dy) { this.move(0, dy); }

Javassist提供了另一種添加新方法的方法。您可以先創建一個抽象方法,然後再給它一個方法主體:

CtClass cc = ... ;
CtMethod m = new CtMethod(CtClass.intType, "move",
                          new CtClass[] { CtClass.intType }, cc);
cc.addMethod(m);
m.setBody("{ x += $1; }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

添加屬性

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f);

如果必須指定添加字段的初始值,則必須將上面顯示的程序修改爲:

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f, "0");    // initial value is 0.

可以簡寫爲一下內容:

CtClass point = ClassPool.getDefault().get("Point");
CtField f = CtField.make("public int z = 0;", point);
point.addField(f);

刪除

要刪除字段或方法,請在CtClass中調用removeField()或removeMethod()。可以通過CtClass中的removeConstructor()刪除CtConstructor。

註釋

CtClass,CtMethod,CtField和CtConstructor提供了一種方便的方法getAnnotations()來讀取註釋。它返回一個註釋類型的對象。

public @interface Author {
    String name();
    int year();
}
This annotation is used as the following:

@Author(name="Chiba", year=2005)
public class Point {
    int x, y;
}
//Then, the value of the annotation can be obtained by getAnnotations(). It returns an array containing annotation-type objects.

CtClass cc = ClassPool.getDefault().get("Point");
Object[] all = cc.getAnnotations();
Author a = (Author)all[0];
String name = a.name();
int year = a.year();
System.out.println("name: " + name + ", year: " + year);


結果:
name: Chiba, year: 2005

##運行時支持類

在大多數情況下,由Javassist修改的類不需要運行Javassist。但是,由Javassist編譯器生成的某些字節碼需要運行時支持類,這些類位於javassist.runtime包中(有關詳細信息,請閱讀該包的API參考)。請注意,javassist.runtime軟件包是Javassist修改的類可能需要運行的唯一軟件包。其他Javassist類從不會在修改後的類的運行時使用。

導入限定名

源代碼中的所有類名稱都必須是完全限定的(它們必須包含程序包名稱)。但是,java.lang包是一個例外。例如,Javassist編譯器可以解析Object以及java.lang.Object。

要告訴編譯器在解析類名稱時搜索其他軟件包,請在ClassPool中調用importPackage()。例如,

ClassPool pool = ClassPool.getDefault();
pool.importPackage("java.awt");
CtClass cc = pool.makeClass("Test");
CtField f = CtField.make("public Point p;", cc);
cc.addField(f);

限制

在當前的實現中,Javassist中包含的Java編譯器相對於編譯器可以接受的語言有一些限制。這些限制是:
不支持J2SE 5.0引入的新語法(包括枚舉和泛型)。Javassist的低級API支持註釋。請參見javassist.bytecode.annotation包以及CtClass和CtBehavior中的getAnnotations())。泛型也僅部分受支持。有關更多詳細信息,請參見後一部分。

  • 除非數組維爲1,否則數組初始化器(用大括號{和}括起來的表達式的逗號分隔列表)不可用。
  • 不支持內部類或匿名類。請注意,這僅是編譯器的限制。它不能編譯包括匿名類聲明的源代碼。Javassist可以讀取和修改內部/匿名類的類文件。
  • 不支持標記爲 continue 和 break 的語句。
  • 編譯器未正確實現Java方法分派算法。如果類中定義的方法具有相同的名稱但採用不同的參數列表,則編譯器可能會感到困惑。

字節碼級別的API

略 詳情看http://www.javassist.org/tutorial/tutorial3.html 需要了解Java 字節碼底層知識,如果想使用底層操作,,推薦 ASM

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章