1. 程式人生 > >Java學習之javassist

Java學習之javassist

javassist可以實現動態程式設計,即動態生成class檔案,或者操作class檔案,下面就詳細介紹。

1、讀取和輸出位元組碼

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 //會從classpath中查詢該類

  3. 3 CtClass cc = pool.get("test.Rectangle");

  4. 4 //設定.Rectangle的父類

  5. 5 cc.setSuperclass(pool.get("test.Point"));

  6. 6 //輸出.Rectangle.class檔案到該目錄中

  7. 7 cc.writeFile("c://");

  8. 8 //輸出成二進位制格式

  9. 9 //byte[] b=cc.toBytecode();

  10. 10 //輸出並載入class 類,預設載入到當前執行緒的ClassLoader中,也可以選擇輸出的ClassLoader。

  11. 11 //Class clazz=cc.toClass();

這裡可以看出,Javassist的載入是依靠ClassPool類,輸出方式支援三種。

2、新增Class

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 CtClass cc = pool.makeClass("Point");

  3. 3 //新增方法

  4. 4 cc.addMethod(m);

  5. 5 //新增Field

  6. 6 cc.addField(f);

從上面可以看出,對Class的修改主要是依賴於CtClass類。API也比較清楚和簡單。

3、凍結Class

    當CtClass 呼叫writeFile()、toClass()、toBytecode() 這些方法的時候,Javassist會凍結CtClass Object,對CtClass object的修改將不允許。這個主要是為了警告開發者該類已經被載入,而JVM是不允許重新載入該類的。如果要突破該限制,方法如下:

 
  1. 1 CtClasss cc = ...;

  2. 2 :

  3. 3 cc.writeFile();

  4. 4 cc.defrost();

  5. 5 cc.setSuperclass(...); // OK since the class is not frozen.

    當 ClassPool.doPruning=true的時候,Javassist 在CtClass object被凍結時,會釋放儲存在ClassPool對應的資料。這樣做可以減少javassist的記憶體消耗。預設情況ClassPool.doPruning=false。例如 

 
  1. 1 CtClasss cc = ...;

  2. 2 cc.stopPruning(true);

  3. 3 :

  4. 4 cc.writeFile(); // convert to a class file.

  5. 5 // cc沒有被釋放

提示:當除錯時,可以呼叫debugWriteFile(),該方法不會導致CtClass被釋放。

4、Class 搜尋路徑

    從上面可以看出Class 的載入是依靠ClassPool,而ClassPool.getDefault() 方法的搜尋Classpath 只是搜尋JVM的同路徑下的class。當一個程式執行在JBoss或者Tomcat下,ClassPool Object 可能找到使用者的classes。Javassist 提供了四種動態載入classpath的方法。如下

 
  1. 1 //預設載入方式如pool.insertClassPath(new ClassClassPath(this.getClass()));

  2. 2 ClassPool pool = ClassPool.getDefault();

  3. 3 //從file載入classpath

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

  5. 5 //從URL中載入

  6. 6 ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");

  7. 7 pool.insertClassPath(cp);

  8. 8 //從byte[] 中載入

  9. 9 byte[] b = a byte array;

  10. 10 String name = class name;

  11. 11 cp.insertClassPath(new ByteArrayClassPath(name, b));

  12. 12 //可以從輸入流中載入class

  13. 13 InputStream ins = an input stream for reading a class file;

  14. 14 CtClass cc = cp.makeClass(ins);

5、ClassPool

5.1 減少記憶體溢位

     ClassPool是一個CtClass objects的裝載容器,當載入了CtClass object後,是不會被ClassPool釋放的(預設情況下),這個是因為CtClass object 有可能在下個階段會被用到,當載入過多的CtClass object的時候,會造成OutOfMemory的異常。為了避免這個異常,javassist提供幾種方法,一種是在上面提到的 ClassPool.doPruning這個引數,還有一種方法是呼叫CtClass.detach()方法,可以把CtClass object 從ClassPool中移除。例如:

 
  1. 1 CtClass cc = ... ;

  2. 2 cc.writeFile();

  3. 3 cc.detach();

    另外一種方法是不用預設的ClassPool即不用 ClassPool.getDefault()這個方式來生成,這樣當ClassPool沒被引用的時候,JVM的垃圾收集會收集該類。例如

 
  1. 1 //ClassPool(true) 會預設載入Jvm的ClassPath

  2. 2 ClassPool cp = new ClassPool(true);

  3. 3 // if needed, append an extra search path by appendClassPath()

5.2  級聯ClassPools

     javassist支援級聯的ClassPool,即類似於繼承。例如:

 
  1. 1 ClassPool parent = ClassPool.getDefault();

  2. 2 ClassPool child = new ClassPool(parent);

  3. 3 child.insertClassPath("./classes");

5.3 修改已有Class的name以建立一個新的Class

    當呼叫setName方法時,會直接修改已有的Class的類名,如果再次使用舊的類名,則會重新在classpath路徑下載入。例如:

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 CtClass cc = pool.get("Point");

  3. 3 cc.setName("Pair");

  4. 4 //重新在classpath載入

  5. 5 CtClass cc1 = pool.get("Point"); 

    對於一個被凍結(Frozen)的CtClass object ,是不可以修改class name的,如果需要修改,則可以重新載入,例如:

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 CtClass cc = pool.get("Point");

  3. 3 cc.writeFile(); // has frozened

  4. 4 //cc.setName("Pair"); wrong since writeFile() has been called.

  5. 5 CtClass cc2 = pool.getAndRename("Point", "Pair"); 

6、Class loader

    上面也提到,javassist同個Class是不能在同個ClassLoader中載入兩次的,所以在輸出CtClass的時候需要注意下,例如:

 
  1. 1 // 當Hello未載入的時候,下面是可以執行的。

  2. 2 ClassPool cp = ClassPool.getDefault();

  3. 3 CtClass cc = cp.get("Hello");

  4. 4 Class c = cc.toClass();

  5. 5 //下面這種情況,由於Hello2已載入,所以會出錯

  6. 6 Hello2 h=new Hello2();

  7. 7 CtClass cc2 = cp.get("Hello2");

  8. 8 Class c2 = cc.toClass();//這裡會丟擲java.lang.LinkageError 異常

  9. 9 //解決載入問題,可以指定一個未載入的ClassLoader

  10. 10 Class c3 = cc.toClass(new MyClassLoader());

6.1 使用javassist.Loader

    從上面可以看到,如果在同一個ClassLoader載入兩次Class丟擲異常,為了方便javassist也提供一個Classloader供使用,例如

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 Loader cl = new Loader(pool);

  3. 3 CtClass ct = pool.get("test.Rectangle");

  4. 4 ct.setSuperclass(pool.get("test.Point"));

  5. 5 Class c = cl.loadClass("test.Rectangle");

  6. 6 Object rect = c.newInstance();        :

   為了方便監聽Javassist自帶的ClassLoader的生命週期,javassist也提供了一個listener,可以監聽ClassLoader的生命週期,例如:

 
  1. 1 //Translator 為監聽器

  2. 2 public class MyTranslator implements Translator {

  3. 3 void start(ClassPool pool)

  4. 4 throws NotFoundException, CannotCompileException {}

  5. 5 void onLoad(ClassPool pool, String classname)

  6. 6 throws NotFoundException, CannotCompileException

  7. 7 {

  8. 8 CtClass cc = pool.get(classname);

  9. 9 cc.setModifiers(Modifier.PUBLIC);

  10. 10 }

  11. 11 }

  12. 12 //示例

  13. 13 public class Main2 {

  14. 14 public static void main(String[] args) throws Throwable {

  15. 15 Translator t = new MyTranslator();

  16. 16 ClassPool pool = ClassPool.getDefault();

  17. 17 Loader cl = new Loader();

  18. 18 cl.addTranslator(pool, t);

  19. 19 cl.run("MyApp", args);

  20. 20 }

  21. 21 }

  22. 22 //輸出

  23. 23 % java Main2 arg1 arg2...

6.2 修改系統Class

    由JVM規範可知,system classloader 是比其他classloader 是優先載入的,而system classloader 主要是載入系統Class,所以要修改系統Class,如果預設引數執行程式是不可能修改的,如果需要修改也有一些辦法,即在執行時加入-Xbootclasspath/p: 引數的意義可以參考其他檔案。下面修改String的例子如下:

 
  1. ClassPool pool = ClassPool.getDefault();

  2. CtClass cc = pool.get("java.lang.String");

  3. CtField f = new CtField(CtClass.intType, "hiddenValue", cc);

  4. f.setModifiers(Modifier.PUBLIC);

  5. cc.addField(f);

  6. cc.writeFile(".");

  7. //執行指令碼

  8. % java -Xbootclasspath/p:. MyApp arg1 arg2...

6.3 動態過載Class

    如果JVM執行時開啟JPDA(Java Platform Debugger Architecture),則Class是執行被動態重新載入的。具體方式可以參考java.lang.Instrument。javassist也提供了一個執行期過載Class的方法,具體可以看API 中的javassist.tools.HotSwapper。

7、Introspection和定製

    javassist封裝了很多很方便的方法以供使用,大部分使用只需要用這些API即可,如果不能滿足,Javassist也提供了一個低層的API(具體參考javassist.bytecode 包)來修改原始的Class。

7.1 插入source 文字在方法體前或者後

     CtMethod 和CtConstructor 提供了 insertBefore()、insertAfter()和 addCatch()方法,它們可以插入一個souce文字到存在的方法的相應的位置。javassist 包含了一個簡單的編譯器解析這souce文字成二進位制插入到相應的方法體裡。javassist 還支援插入一個程式碼段到指定的行數,前提是該行數需要在class 檔案裡含有。插入的source 可以關聯fields 和methods,也可以關聯方法的引數。但是關聯方法引數的時,需要在程式編譯時加上 -g 選項(該選項可以把本地變數的宣告儲存在class 檔案中,預設是不加這個引數的。)。因為預設一般不加這個引數,所以Javassist也提供了一些特殊的變數來代表方法引數:$1,$2,$args...要注意的是,插入的source文字中不能引用方法本地變數的宣告,但是可以允許宣告一個新的方法本地變數,除非在程式編譯時加入-g選項。方法的特殊變數說明:

$0, $1, $2, ... this and actual parameters
$args An array of parameters. The type of $args is Object[].
$$ All actual parameters.For example, m($$) is equivalent to m($1,$2,...)
$cflow(...) cflow variable
$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
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.

 

7.1.1 $0, $1, $2, ...

   $0程式碼的是this,$1代表方法引數的第一個引數、$2代表方法引數的第二個引數,以此類推,$N代表是方法引數的第N個。例如:

 
  1. 1 //實際方法

  2. 2 void move(int dx, int dy)

  3. 3 //javassist

  4. 4 CtMethod m = cc.getDeclaredMethod("move");

  5. 5 //列印dx,和dy

  6. 6 m.insertBefore("{ System.out.println($1); System.out.println($2); }");

注意:如果javassist改變了$1的值,那實際引數值也會改變。

7.1.2 $args

    $args 指的是方法所有引數的陣列,類似Object[],如果引數中含有基本型別,則會轉成其包裝型別。需要注意的時候,$args[0]對應的是$1,而不是$0,$0!=$args[0],$0=this。

7.1.3 $$

    $$是所有方法引數的簡寫,主要用在方法呼叫上。例如:

 
  1. 1 //原方法

  2. 2 move(String a,String b)

  3. 3 move($$) 相當於move($1,$2)

  4. 4 如果新增一個方法,方法含有move的所有引數,則可以這些寫:

  5. 5 exMove($$, context) 相當於 exMove($1, $2, context)

7.1.4 $cflow

 $cflow意思為控制流(control flow),是一個只讀的變數,值為一個方法呼叫的深度。例如:

 
  1. 1 //原方法

  2. 2 int fact(int n) {

  3. 3 if (n <= 1)

  4. 4 return n;

  5. 5 else

  6. 6 return n * fact(n - 1);

  7. 7 }

  8. 8 //javassist呼叫

  9. 9 CtMethod cm = ...;

  10. 10 //這裡代表使用了cflow

  11. 11 cm.useCflow("fact");

  12. 12 //這裡用了cflow,說明當深度為0的時候,就是開始當第一次呼叫fact的方法的時候,列印方法的第一個引數

  13. 13 cm.insertBefore("if ($cflow(fact) == 0)"

  14. 14 + " System.out.println(\"fact \" + $1);");

7.1.5 $r

   指的是方法返回值的型別,主要用在型別的轉型上。例如:

Object result = ... ;

$_ = ($r)result;

如果返回值為基本型別的包裝型別,則該值會自動轉成基本型別,如返回值為Integer,則$r為int。如果返回值為void,則該值為null。

7.1.6 $w

$w代表一個包裝型別。主要用在轉型上。比如:Integer i = ($w)5; 如果該型別不是基本型別,則會忽略。

7.1.7 $_

$_代表的是方法的返回值。

7.1.8 $sig

$sig指的是方法引數的型別(Class)陣列,陣列的順序為引數的順序。

7.1.9 $class

$class 指的是this的型別(Class)。也就是$0的型別。

7.1.10 addCatch()

   addCatch() 指的是在方法中加入try catch 塊,需要注意的是,必須在插入的程式碼中,加入return 值。$e代表異常值。比如:

 
  1. 1 CtMethod m = ...;

  2. 2 CtClass etype = ClassPool.getDefault().get("java.io.IOException");

  3. 3 m.addCatch("{ System.out.println($e); throw $e; }", etype);

實際程式碼如下:

 
  1. 1 try {

  2. 2 the original method body

  3. 3 }

  4. 4 catch (java.io.IOException e) {

  5. 5 System.out.println(e);

  6. 6 throw e;

  7. 7 }

8、修改方法體

CtMethod 和CtConstructor 提供了 setBody() 的方法,可以替換方法或者建構函式裡的所有內容。

支援的變數有:

$0, $1, $2, ... this and actual parameters
$args An array of parameters. The type of $args is Object[].
$$ All actual parameters.For example, m($$) is equivalent to m($1,$2,...)
$cflow(...) cflow variable
$r The result type. It is used in a cast expression.
$w The wrapper type. It is used in a cast expression.
$sig An array of java.lang.Class objects representing the formal parameter types
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.

注意 $_變數不支援。

8.1 替換方法中存在的source

javassist 允許修改方法裡的其中一個表示式,javassist.expr.ExprEditor 這個class 可以替換該表示式。例如:

 
  1. 1 CtMethod cm = ... ;

  2. 2 cm.instrument(

  3. 3 new ExprEditor() {

  4. 4 public void edit(MethodCall m)

  5. 5 throws CannotCompileException

  6. 6 {

  7. 7 if (m.getClassName().equals("Point")

  8. 8 && m.getMethodName().equals("move"))

  9. 9 m.replace("{ $1 = 0; $_ = $proceed($$); }");

  10. 10 }

  11. 11 });

    注意: that the substituted code is not an expression but a statement or a block. It cannot be or contain a try-catch statement.

方法instrument() 可以用來搜尋方法體裡的內容。比如呼叫一個方法,field訪問,物件建立等。如果你想在某個表示式前後插入方法,則修改的souce如下:

{ before-statements;

  $_ = $proceed($$);

  after-statements; } 

8.2 javassist.expr.MethodCall

MethodCall代表的是一個方法的呼叫。用replace()方法可以對呼叫的方法進行替換。

$0 The target object of the method call.
This is not equivalent to this, which represents the caller-side this object.
$0 is null if the method is static.
$1, $2, ... The parameters of the method call.
$_ The resulting value of the method call.
$r The result type of the method call.
$class A java.lang.Class object representing the class declaring the method.
$sig An array of java.lang.Class objects representing the formal parameter types
$type A java.lang.Class object representing the formal result type.
$proceed The name of the method originally called in the expression.

注意:$w, $args 和 $$也是允許的。$0不是this,是隻呼叫方法的Object。$proceed指的是一個特殊的語法,而不是一個String。

8.3 javassist.expr.ConstructorCall

ConstructorCall 指的是一個建構函式,比如:this()、super()的呼叫。ConstructorCall.replace()是用來用替換一個塊當呼叫構造方法的時候。

$0 The target object of the constructor call. This is equivalent to this.
$1, $2, ... The parameters of the constructor call.
$class A java.lang.Class object representing the class declaring the constructor.
$sig An array of java.lang.Class objects representing the formal parameter types.
$proceed The name of the constructor originally called in the expression.

$w, $args 和 $$  也是允許的。

8.4 javassist.expr.FieldAccess

FieldAccess代表的是Field的訪問類。

$0 The object containing the field accessed by the expression. This is not equivalent to this.
this represents the object that the method including the expression is invoked on.
$0 is null if the field is static.
$1 The value that would be stored in the field if the expression is write access.
Otherwise, $1 is not available.
$_ The resulting value of the field access if the expression is read access.
Otherwise, the value stored in $_ is discarded.
$r The type of the field if the expression is read access.
Otherwise, $r is void.
$class A java.lang.Class object representing the class declaring the field.
$type A java.lang.Class object representing the field type.
$proceed The name of a virtual method executing the original field access. .

$w, $args 和 $$  也是允許的。 

8.5 javassist.expr.NewExpr

NewExpr代表的是一個Object 的操作(但不包括陣列的建立)。

$0 null
$1, $2, ... The parameters to the constructor.
$_ The resulting value of the object creation.
A newly created object must be stored in this variable.
$r The type of the created object.
$sig An array of java.lang.Class objects representing the formal parameter types
$type A java.lang.Class object representing the class of the created object.
$proceed The name of a virtual method executing the original object creation. .

$w, $args 和 $$  也是允許的。

8.6 javassist.expr.NewArray

NewArray 代表的是陣列的建立。

$0 null
$1, $2, ... The size of each dimension.
$_ The resulting value of the object creation. 
A newly created array must be stored in this variable.
$r The type of the created object.
$type A java.lang.Class object representing the class of the created array .
$proceed The name of a virtual method executing the original array creation. .

$w, $args 和 $$  也是允許的。

例如:

String[][] s = new String[3][4];

 $1 和 $2 的值為 3 和 4, $3 得不到的.

String[][] s = new String[3][];

 $1 的值是 3 ,但 $2 得不到的.

8.7 javassist.expr.Instanceof

Instanceof 代表的是Instanceof 表示式。

$0 null
$1 The value on the left hand side of the original instanceof operator.
$_ The resulting value of the expression. The type of $_ is boolean.
$r The type on the right hand side of the instanceof operator.
$type A java.lang.Class object representing the type on the right hand side of the instanceof operator.
$proceed The name of a virtual method executing the original instanceof expression.
It takes one parameter (the type is java.lang.Object) and returns true
if the parameter value is an instance of the type on the right hand side of
the original instanceof operator. Otherwise, it returns false.

$w, $args 和 $$  也是允許的。 

8.8 javassist.expr.Cast

Cast 代表的是一個轉型表示式。

$0 null
$1 The value the type of which is explicitly cast.
$_ The resulting value of the expression. The type of $_ is the same as the type
after the explicit casting, that is, the type surrounded by ( ).
$r the type after the explicit casting, or the type surrounded by ( ).
$type A java.lang.Class object representing the same type as $r.
$proceed The name of a virtual method executing the original type casting.
It takes one parameter of the type java.lang.Object and returns it after
the explicit type casting specified by the original expression.

$w, $args 和 $$  也是允許的。 

8.9 javassist.expr.Handler

Handler 代表的是一個try catch 宣告。

$1 The exception object caught by the catch clause.
$r the type of the exception caught by the catch clause. It is used in a cast expression.
$w The wrapper type. It is used in a cast expression.
$type A java.lang.Class object representing
the type of the exception caught by the catch clause.

9 新增一個方法或者field

Javassist 允許開發者建立一個新的方法或者構造方法。新增一個方法,例如:

 
  1. 1 CtClass point = ClassPool.getDefault().get("Point");

  2. 2 CtMethod m = CtNewMethod.make(

  3. 3 "public int xmove(int dx) { x += dx; }",

  4. 4 point);

  5. 5 point.addMethod(m);

  6. 6

  7. 7 在方法中呼叫其他方法,例如:

  8. 8 CtClass point = ClassPool.getDefault().get("Point");

  9. 9 CtMethod m = CtNewMethod.make(

  10. 10 "public int ymove(int dy) { $proceed(0, dy); }",

  11. 11 point, "this", "move");

  12. 12 其效果如下:

  13. 13 public int ymove(int dy) { this.move(0, dy); }

下面是javassist提供另一種新增一個方法(未看明白):

Javassist provides another way to add a new method. You can first create an abstract method and later give it a method body:

 
  1. 1 CtClass cc = ... ;

  2. 2 CtMethod m = new CtMethod(CtClass.intType, "move",

  3. 3 new CtClass[] { CtClass.intType }, cc);

  4. 4 cc.addMethod(m);

  5. 5 m.setBody("{ x += $1; }");

  6. 6 cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

  7. 7 Since Javassist makes a class abstract if an abstract method is added to the class, you have to explicitly change the class back to a non-abstract one after calling setBody().

9.1 遞迴方法

 
  1. 1 CtClass cc = ... ;

  2. 2 CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc);

  3. 3 CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc);

  4. 4 cc.addMethod(m);

  5. 5 cc.addMethod(n);

  6. 6 m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }");

  7. 7 n.setBody("{ return m($1); }");

  8. 8 cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

9.2 新增field

如下:

 
  1. 1 CtClass point = ClassPool.getDefault().get("Point");

  2. 2 CtField f = new CtField(CtClass.intType, "z", point);

  3. 3 point.addField(f);

  4. 4 //point.addField(f, "0"); // initial value is 0.

  5. 5 或者:

  6. 6 CtClass point = ClassPool.getDefault().get("Point");

  7. 7 CtField f = CtField.make("public int z = 0;", point);

  8. 8 point.addField(f);

9.3 移除方法或者field

1 呼叫removeField()或者removeMethod()。

10 註解

獲取註解資訊:

 
  1. 1 //註解

  2. 2 public @interface Author {

  3. 3 String name();

  4. 4 int year();

  5. 5 }

  6. 6 //javassist程式碼

  7. 7 CtClass cc = ClassPool.getDefault().get("Point");

  8. 8 Object[] all = cc.getAnnotations();

  9. 9 Author a = (Author)all[0];

  10. 10 String name = a.name();

  11. 11 int year = a.year();

  12. 12 System.out.println("name: " + name + ", year: " + year);

11  javassist.runtime 

12 import

引用包:

 
  1. 1 ClassPool pool = ClassPool.getDefault();

  2. 2 pool.importPackage("java.awt");

  3. 3 CtClass cc = pool.makeClass("Test");

  4. 4 CtField f = CtField.make("public Point p;", cc);

  5. 5 cc.addField(f);

13 限制

(1)不支援java5.0的新增語法。不支援註解修改,但可以通過底層的javassist類來解決,具體參考:javassist.bytecode.annotation

(2)不支援陣列的初始化,如String[]{"1","2"},除非只有陣列的容量為1

(3)不支援內部類和匿名類

(4)不支援continue和btreak 表示式。

(5)對於繼承關係,有些不支援。例如

class A {} 

class B extends A {} 

class C extends B {} 

 

class X { 

    void foo(A a) { .. } 

    void foo(B b) { .. } 

}

如果呼叫  x.foo(new C()),可能會呼叫foo(A) 。

(6)推薦開發者用#分隔一個class name和static method或者 static field。例如:

javassist.CtClass.intType.getName()推薦用javassist.CtClass#intType.getName()

14.完整例項

14.1 建立類例項

 
  1. 1 package com.swust.javassist;

  2. 2

  3. 3 import javassist.ClassPool;

  4. 4 import javassist.CtClass;

  5. 5 import javassist.CtConstructor;

  6. 6 import javassist.CtField;

  7. 7 import javassist.CtMethod;

  8. 8

  9. 9 public class Example1 {

  10. 10 public static void main(String[] args) throws Exception {

  11. 11 ClassPool pool = ClassPool.getDefault();

  12. 12 CtClass cc = pool.makeClass("bean.User");

  13. 13

  14. 14 //建立屬性

  15. 15 CtField field01 = CtField.make("private int id;",cc);

  16. 16 CtField field02 = CtField.make("private String name;", cc);

  17. 17 cc.addField(field01);

  18. 18 cc.addField(field02);

  19. 19

  20. 20 //建立方法

  21. 21 CtMethod method01 = CtMethod.make("public String getName(){return name;}", cc);

  22. 22 CtMethod method02 = CtMethod.make("public void setName(String name){this.name = name;}", cc);

  23. 23 cc.addMethod(method01);

  24. 24 cc.addMethod(method02);

  25. 25

  26. 26 //新增有參構造器

  27. 27 CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")},cc);

  28. 28 constructor.setBody("{this.id=id;this.name=name;}");

  29. 29 cc.addConstructor(constructor);

  30. 30 //無參構造器

  31. 31 CtConstructor cons = new CtConstructor(null,cc);

  32. 32 cons.setBody("{}");

  33. 33 cc.addConstructor(cons);

  34. 34

  35. 35 cc.writeFile("E:/workspace/TestCompiler/src");

  36. 36 }

  37. 37 }

14.2 訪問類例項變數

 
  1. 1 package com.swust.javassist;

  2. 2

  3. 3 import java.lang.reflect.Field;

  4. 4 import java.lang.reflect.Method;

  5. 5 import java.util.Arrays;

  6. 6

  7. 7 import javassist.ClassPool;

  8. 8 import javassist.CtClass;

  9. 9 import javassist.CtConstructor;

  10. 10 import javassist.CtField;

  11. 11 import javassist.CtMethod;

  12. 12 import javassist.CtNewMethod;

  13. 13 import javassist.Modifier;

  14. 14

  15. 15 public class Example2 {

  16. 16 //獲取類的簡單資訊

  17. 17 public static void test01() throws Exception{

  18. 18 ClassPool pool = ClassPool.getDefault();

  19. 19 CtClass cc = pool.get("com.swust.beans.Person");

  20. 20 //得到位元組碼

  21. 21 byte[] bytes = cc.toBytecode();

  22. 22 System.out.println(Arrays.toString(bytes));

  23. 23 System.out.println(cc.getName());//獲取類名

  24. 24 System.out.println(cc.getSimpleName());//獲取簡要類名

  25. 25 System.out.println(cc.getSuperclass());//獲取父類

  26. 26 System.out.println(cc.getInterfaces());//獲取介面

  27. 27 System.out.println(cc.getMethods());//獲取

  28. 28 }

  29. 29 //新生成一個方法

  30. 30 public static void test02() throws Exception{

  31. 31 ClassPool pool = ClassPool.getDefault();

  32. 32 CtClass cc = pool.get("com.swust.beans.Person");

  33. 33 //第一種

  34. 34 //CtMethod cm = CtMethod.make("public String getName(){return name;}", cc);

  35. 35 //第二種

  36. 36 //引數:返回值型別,方法名,引數,物件

  37. 37 CtMethod cm = new CtMethod(CtClass.intType,"add",new CtClass[]{CtClass.intType,CtClass.intType},cc);

  38. 38 cm.setModifiers(Modifier.PUBLIC);//訪問範圍

  39. 39 cm.setBody("{return $1+$2;}");

  40. 40 //cc.removeMethod(m) 刪除一個方法

  41. 41 cc.addMethod(cm);

  42. 42 //通過反射呼叫方法

  43. 43 Class clazz = cc.toClass();

  44. 44 Object obj = clazz.newInstance();//通過呼叫無參構造器,生成新的物件

  45. 45 Method m = clazz.getDeclaredMethod("add", int.class,int.class);

  46. 46 Object result = m.invoke(obj, 2,3);

  47. 47 System.out.println(result);

  48. 48 }

  49. 49

  50. 50 //修改已有的方法

  51. 51 public static void test03() throws Exception{

  52. 52 ClassPool pool = ClassPool.getDefault();

  53. 53 CtClass cc = pool.get("bean.User");

  54. 54

  55. 55 CtMethod cm = cc.getDeclaredMethod("hello",new CtClass[]{pool.get("java.lang.String")});

  56. 56 cm.insertBefore("System.out.println(\"呼叫前\");");//呼叫前

  57. 57 cm.insertAt(29, "System.out.println(\"29\");");//行號

  58. 58 cm.insertAfter("System.out.println(\"呼叫後\");");//呼叫後

  59. 59

  60. 60 //通過反射呼叫方法

  61. 61 Class clazz = cc.toClass();

  62. 62 Object obj = clazz.newInstance();

  63. 63 Method m = clazz.getDeclaredMethod("hello", String.class);

  64. 64 Object result = m.invoke(obj, "張三");

  65. 65 System.out.println(result);

  66. 66 }

  67. 67

  68. 68 //修改已有屬性

  69. 69 public static void test04() throws Exception{

  70. 70 ClassPool pool = ClassPool.getDefault();

  71. 71 CtClass cc = pool.get("bean.User");

  72. 72

  73. 73 //屬性

  74. 74 CtField cf = new CtField(CtClass.intType,"age",cc);

  75. 75 cf.setModifiers(Modifier.PRIVATE);

  76. 76 cc.addField(cf);

  77. 77 //增加響應的get set方法

  78. 78 cc.addMethod(CtNewMethod.getter("getAge",cf));

  79. 79 cc.addMethod(CtNewMethod.setter("setAge",cf));

  80. 80

  81. 81 //訪問屬性

  82. 82 Class clazz = cc.toClass();

  83. 83 Object obj = clazz.newInstance();

  84. 84 Field field = clazz.getDeclaredField("age");

  85. 85 System.out.println(field);

  86. 86 Method m = clazz.getDeclaredMethod("setAge", int.class);

  87. 87 m.invoke(obj, 16);

  88. 88 Method m2 = clazz.getDeclaredMethod("getAge", null);

  89. 89 Object resutl = m2.invoke(obj,null);

  90. 90 System.out.println(resutl);

  91. 91 }

  92. 92

  93. 93 //操作構造方法

  94. 94 public static void test05() throws Exception{

  95. 95 ClassPool pool = ClassPool.getDefault();

  96. 96 CtClass cc = pool.get("com.swust.beans.Person");

  97. 97

  98. 98 CtConstructor[] cons = cc.getConstructors();

  99. 99 for(CtConstructor con:cons){

  100. 100 System.out.println(con);

  101. 101 }

  102. 102 }

  103. 103 public static void main(String[] args) throws Exception {

  104. 104 test01();

  105. 105 //test02();

  106. 106 //test03();

  107. 107 //test04();

  108. 108 test05();

  109. 109 }

  110. 110 }

呼叫方法1獲取類的基本資訊,結果如下:

 
  1. 1 完整類名為:com.swust.beans.Person

  2. 2 類名為:Person

  3. 3 父類名稱為:java.lang.Object

  4. 4 *****************************

  5. 5 *****************************

  6. 6 屬性方法為:wait

  7. 7 屬性方法為:wait

  8. 8 屬性方法為:setName

  9. 9 屬性方法為:notifyAll

  10. 10 屬性方法為:wait

  11. 11 屬性方法為:toString

  12. 12 屬性方法為:getName

  13. 13 屬性方法為:setAge

  14. 14 屬性方法為:equals

  15. 15 屬性方法為:main

  16. 16 屬性方法為:getAge

  17. 17 屬性方法為:getClass

  18. 18 屬性方法為:clone

  19. 19 屬性方法為:finalize

  20. 20 屬性方法為:hashCode

  21. 21 屬性方法為:notify

呼叫方法2新增新方法:

1 方法執行結果為:5
 
  1. 1 這是在原有方法體執行之前增加的內容

  2. 2 張三

  3. 3 這是在原有方法體執行之後增加的內容

  4. 4 null

呼叫方法4修改已有屬性:

 
  1. 1 增添的屬性為:private int com.s

  2. wust.beans.Person.age

  3. 2 getAge方法執行後的結果為:16

  4. 3 增添的屬性為:private int com.swust.beans.Person.height

  5. 4 getHeight方法執行後的結果為:176

呼叫方法5操作建構函式:

1 [email protected][public Person ()V]

from: https://www.cnblogs.com/sunfie/p/5154246.html