1. 程式人生 > >Javassist 字節碼 反編譯 語法 案例 MD

Javassist 字節碼 反編譯 語法 案例 MD

lena 出錯 write taf pos 解決 turn row 你好

Markdown版本筆記 我的GitHub首頁 我的博客 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

Javassist 字節碼 反編譯 語法 案例 MD


目錄

目錄
簡介
語法
Class 搜索路徑
讀取和輸出字節碼
凍結Class
ClassPool
減少內存溢出
級聯ClassPools
修改已有Class的name以創建一個新的Class
Class loader
使用 javassist.Loader
修改系統Class
動態重載Class
Introspection 和定制
插入 source 文本在方法體前或者後
類型簡介
addCatch()
修改方法體
替換方法中存在的 source
MethodCall
ConstructorCall
FieldAccess
NewExpr
NewArray
Instanceof
Cast
Handler
新增一個方法或者field
遞歸方法
新增field
移除方法或者field
註解
引用包 import
限制
案例
創建類實例
常見操作
原始類
test02
test03
test04

簡介

官網
GitHub
參考

Java bytecode engineering toolkit since 1999

Javassist(Java Programming Assistant)使Java字節碼操作變得簡單。它是一個用於在Java中編輯字節碼的類庫;它使Java程序能夠在運行時定義新類,並在JVM加載時修改類文件。

與其他類似的字節碼編輯器不同,Javassist提供兩個級別的API:源級別和字節碼級別。如果用戶使用源級API,他們可以在不了解Java字節碼規範的情況下編輯class文件。整個API僅使用Java語言的詞匯表進行設計。您甚至可以以源文本的形式指定插入的字節碼; Javassist即時

編譯它。另一方面,字節碼級API允許用戶直接編輯class文件作為其他編輯器。

語法

Class 搜索路徑

Class 的載入是依靠 ClassPool,而 ClassPool.getDefault() 方法的搜索 Classpath 只是搜索 JVM 的同路徑下的 class。當一個程序運行在 JBoss 或者 Tomcat 下,ClassPool Object 可能找到用戶的 classes。Javassist 提供了四種動態加載classpath的方法。如下

ClassPool pool = ClassPool.getDefault();

//默認加載方式如 pool.insertClassPath(new ClassClassPath(this.getClass()));
pool.insertClassPath("D:/test/gson-2.8.1.jar");//從文件加載
pool.insertClassPath(new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist."));//從URL中加載
pool.insertClassPath(new ByteArrayClassPath("name", new byte[0]));//從byte[] 中加載

CtClass cc = cp.makeClass(inputStream);//可以從輸入流中加載class

讀取和輸出字節碼

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle"); //會從classpath中查詢該類
cc.setSuperclass(pool.get("test.Point"));//設置Rectangle的父類
cc.writeFile("c://");//輸出Rectangle.class文件到該目錄中
//byte[] b=cc.toBytecode(); //輸出成二進制格式
//Class clazz=cc.toClass();//輸出並加載class 類,默認加載到當前線程的ClassLoader中,也可以選擇輸出的ClassLoader

這裏可以看出,Javassist 的加載是依靠 ClassPool 類,輸出方式支持三種。

凍結Class

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

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

ClassPool.doPruning=true 的時候,Javassist 在 CtClass object 被凍結時,會釋放存儲在 ClassPool 對應的數據。這樣做可以減少 javassist 的內存消耗。默認情況ClassPool.doPruning=false。例如:

CtClasss cc = ...;
cc.stopPruning(true);
cc.writeFile();
// cc沒有被釋放

提示:當調試時,可以調用 debugWriteFile(),該方法不會導致 CtClass 被釋放。

ClassPool

減少內存溢出

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

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

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

ClassPool cp = new ClassPool(true); //ClassPool(true) 會默認加載Jvm的ClassPath
// if needed, append an extra search path by appendClassPath()

級聯ClassPools

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

ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");

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

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

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.setName("Pair");
CtClass cc1 = pool.get("Point"); //重新在 classpath 加載

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

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile(); // has frozened
//cc.setName("Pair");//wrong since writeFile() has been called.
CtClass cc2 = pool.getAndRename("Point", "Pair");

Class loader

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

// 當Hello未加載的時候,下面是可以運行的。
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("Hello");
Class c = cc.toClass();

//下面這種情況,由於Hello2已加載,所以會出錯
Hello2 h = new Hello2();
CtClass cc2 = cp.get("Hello2");
Class c2 = cc.toClass();//這裏會拋出java.lang.LinkageError 異常

//解決加載問題,可以指定一個未加載的ClassLoader
Class c3 = cc.toClass(new MyClassLoader());

使用 javassist.Loader

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

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

Loader cl = new Loader(pool);
Class c = cl.loadClass("test.Rectangle");
Object rect = c.newInstance();

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

public  interface Translator {
    void start(ClassPool paramClassPool) throws NotFoundException, CannotCompileException;
    void onLoad(ClassPool paramClassPool, String paramString) throws NotFoundException, CannotCompileException;
}
Loader cl = new Loader();
cl.addTranslator(ClassPool, Translator);

修改系統Class

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

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("java.lang.String");
CtField f = new CtField(CtClass.intType, "hiddenValue", cc);
f.setModifiers(Modifier.PUBLIC);
cc.addField(f);
cc.writeFile(".");

動態重載Class

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

Introspection 和定制

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

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

CtMethod 和 CtConstructor 提供了 insertBefore()、insertAfter() 和 addCatch() 方法,它們可以插入一個 souce 文本到存在的方法的相應的位置。

javassist 包含了一個簡單的編譯器解析這 souce 文本成二進制插入到相應的方法體裏。javassist 還支持插入一個代碼段到指定的行數,前提是該行數需要在 class 文件裏含有。插入的 source 可以關聯 fields 和 methods,也可以關聯方法的參數。但是關聯方法參數的時,需要在程序編譯時加上 -g 選項(該選項可以把本地變量的聲明保存在 class 文件中,默認是不加這個參數的)。因為默認一般不加這個參數,所以 Javassist 也提供了一些特殊的變量來代表方法參數:$1,$2,$args...要註意的是,插入的 source 文本中不能引用方法本地變量的聲明,但是可以允許聲明一個新的方法本地變量,除非在程序編譯時加入-g選項。

方法的特殊變量說明:

變量 代表的意義
$0 this
$1 $2... actual parameters, $n代表是方法參數的第n個
$args An array of parameters 方法所有參數的數組
$$ All actual parameters 所有方法參數的簡寫
$cflow(...) cflow variable 方法調用的深度
$r The result type 方法返回值的類型
$w The wrapper type 包裝類型
$_ The resulting value 方法的返回值
$sig 方法參數的類型數組,數組的順序為參數的順序
$type 方法返回值的類型
$class this 的類型,也就是$0的類型

類型簡介

  • $n

$0代碼的是this,$1代表方法參數的第一個參數、$2代表方法參數的第二個參數,以此類推。例如實際方法:

void move(int dx, int dy) 

在此方法前打印兩個參數的值:

CtMethod m = cc.getDeclaredMethod("move"); //不指定參數時會修改此類中所有方法名為 move 的方法
m.insertBefore("{ System.out.println($1); System.out.println($2); }");//打印dx,和dy
  • $args

$args 指的是方法所有參數的數組,類似Object[],如果參數中含有基本類型,則會轉成其包裝類型。需要註意的時候,$args[0]對應的是$1,而不是$0

  • $$

$$是所有方法參數的簡寫,主要用在方法調用上。
For example, m($$) is equivalent to m($1,$2,...)
例如原方法 move(String a,String b)
move($$) 相當於move($1,$2)
如果新增一個方法,方法含有move的所有參數,則可以這麽寫:exMove($$, context) ,相當於 exMove($1, $2, context)

  • $cflow

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

//原方法
int fact(int n) {
    if (n <= 1) return n;
    else return n * fact(n - 1);
}
//javassist調用
CtMethod cm = ...;
cm.useCflow("fact");//這裏代表使用了cflow
//這裏用了cflow,說明當深度為0的時候,就是開始當第一次調用fact的方法的時候,打印方法的第一個參數
cm.insertBefore("if ($cflow(fact) == 0)"
              + "    System.out.println(\"fact \" + $1);");
  • $r

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

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

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

  • $w代表一個包裝類型,主要用在轉型上。比如:Integer i = ($w)5; 如果該類型不是基本類型,則會忽略。
  • $sig指的是方法參數的類型(Class)數組,數組的順序為參數的順序。An array of java.lang.Class objects representing the formal parameter types
  • $class 指的是this的類型(Class)。也就是$0的類型。A java.lang.Class object representing the class currently edited.

addCatch()

addCatch() 指的是在方法中加入 try catch 塊,需要註意的是,必須在插入的代碼中加入 return 值。$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;
}

修改方法體

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

變量 代表的意義
$0, $1, $2 this and actual parameters
args is Object[].
) is equivalent to m(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.

註意 $_變量(方法的返回值)不支持。

替換方法中存在的 source

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

CtMethod cm = ..;
cm.instrument(new ExprEditor() {
    @Override
    public void edit(MethodCall m) throws CannotCompileException {
        if (m.getClassName().equals("Point") && m.getMethodName().equals("move")) {
            m.replace("{ $1 = 0; $_ = $proceed($$); }");
        }
    }
});

註意:替換代碼不是表達式而是語句或塊。它不能或包含try-catch語句。

方法instrument()可以用來搜索方法體裏的內容,比如調用一個方法,field訪問,對象創建等。如果你想在某個表達式前後插入方法,則修改的souce如下:

{ before-statements;
  $_ = $proceed($$);
  after-statements; }

MethodCall

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

變量 代表的意義
$0 The target object of the method call.
$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。

$0 is not equivalent to this, which represents the caller-side this object.
$0 is null if the method is static.

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$$ 也是允許的。

FieldAccess

FieldAccess 代表的是 Field 的訪問類。

變量 代表的意義
$0 The object containing the field accessed by the expression.
$1 The value that would be stored in the field if the expression is write access. Otherwise, $1 is not available.
_ is discarded.
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$$ 也是允許的。

$0 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.

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$$ 也是允許的。

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$$ 也是允許的。

Instanceof

Instanceof 代表的是 Instanceof 表達式。

變量 代表的意義
$0 null
$1 The value on the left hand side of the original instanceof operator.
_ 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.

$proceed

  • 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$$ 也是允許的。

Cast

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

變量 代表的意義
$0 null
$1 The value the type of which is explicitly cast.
$_ The resulting value of the expression.
$r the type after the explicit casting, or the type surrounded by ( ).
r.
$proceed The name of a virtual method executing the original type casting.
  • $_:The type of $_ is the same as the type after the explicit casting, that is, the type surrounded by ( ).
  • $proceed: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$$ 也是允許的。

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. |

新增一個方法或者field

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

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

在方法中調用其他方法,例如:

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);

如果將抽象方法添加到類中,Javassist 會使類抽象化,則必須在調用 setBody 之後將類顯式更改回非抽象類。

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().

遞歸方法

CtClass cc = ... ;
CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc);
CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc);
cc.addMethod(m);
cc.addMethod(n);
m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }");
n.setBody("{ return m($1); }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);

新增field

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f); //新增Field
//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);

移除方法或者field

調用removeField()或者removeMethod()

註解

public @interface Author {
    String name();
    int year();
}

獲取註解信息:

CtClass cc = ClassPool.getDefault().get("Point");
Object[] all = cc.getAnnotations(); //獲取註解信息:
Author a = (Author)all[0];
System.out.println("name: " + a.name() + ", year: " + a.year());

引用包 import

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

限制

  • 不支持 java5.0 的新增語法。不支持註解修改,但可以通過底層的 javassist 類來解決,具體參考:javassist.bytecode.annotation
  • 不支持數組的初始化,如String[]{"1","2"},除非只有數組的容量為1
  • 不支持內部類和匿名類
  • 不支持 continuebtreak 表達式。
  • 對於繼承關系,有些不支持。例如
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) 。

  • 推薦開發者用#分隔一個 class name 和 static method 或者 static field。例如:
    javassist.CtClass.intType.getName()
    推薦用
    javassist.CtClass#intType.getName()

案例

創建類實例

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

//創建屬性  
cc.addField(CtField.make("private int id;", cc));
cc.addField(CtField.make("private String name;", cc));

//創建方法  
cc.addMethod(CtMethod.make("public String getName(){return name;}", cc));
cc.addMethod(CtMethod.make("public void setName(String name){this.name = name;}", cc));

//添加有參構造器  
CtConstructor constructor = new CtConstructor(new CtClass[] { CtClass.intType, pool.get("java.lang.String") }, cc);
constructor.setBody("{this.id=id;this.name=name;}");
cc.addConstructor(constructor);

//無參構造器  
CtConstructor cons = new CtConstructor(null, cc);
cons.setBody("{}");
cc.addConstructor(cons);

cc.writeFile("d:/test");

生成的類經反編譯後的源碼為:

package bean;

public class User {
    private int id;
    private String name;

    public User() {
    }

    public User(int paramInt, String paramString) {
        this.id = this.id;
        this.name = this.name;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String paramString) {
        this.name = paramString;
    }
}

常見操作

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;

public class Test {

    public static void main(String[] args) throws Exception {
        //        test01();
        //        test02();
        test03();
        //        test04();
        //        test05();
    }

    //獲取類的簡單信息  
    public static void test01() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bqt.test.Person");
        //得到字節碼  
        byte[] bytes = cc.toBytecode();
        System.out.println(bytes.length);
        System.out.println(cc.getName());//獲取類名  
        System.out.println(cc.getSimpleName());//獲取簡要類名  
        System.out.println(cc.getSuperclass().getName());//獲取父類  
        System.out.println(Arrays.toString(cc.getInterfaces()));//獲取接口  
        for (CtMethod method : cc.getMethods()) {//獲取方法
            System.out.println(method.getLongName());
        }
    }

    //添加方法  
    public static void test02() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bqt.test.Person");

        CtMethod cm1 = CtMethod.make("public int add1(int a, int b){return a+b;}", cc);//第一種方式,完整的方法以字符串形式傳遞過去
        cc.addMethod(cm1); //cc.removeMethod(cm3) 刪除一個方法

        CtClass[] parameters = new CtClass[] { CtClass.intType, CtClass.intType };
        CtMethod cm2 = new CtMethod(CtClass.intType, "add2", parameters, cc);//第二種方式,返回值類型,方法名,參數,對象  
        cm2.setModifiers(Modifier.PUBLIC);//訪問範圍  
        cm2.setBody("{return $1+$2;}");
        cc.addMethod(cm2);

        //通過反射調用方法  
        Class clazz = cc.toClass(); //註意,如果使用 JDK9 以下的JDK ,同時使用 3.20.0-GA 以上版本的 Javassist,會報 StackWalker 異常
        Object obj = clazz.newInstance();//通過調用無參構造器,生成新的對象

        Method m1 = clazz.getDeclaredMethod("add1", int.class, int.class);
        System.out.println(m1.invoke(obj, 1, 2));

        Method m2 = clazz.getDeclaredMethod("add2", int.class, int.class);
        System.out.println(m2.invoke(obj, 2, 3));

        cc.writeFile("D:/test");//保存到指定位置
    }

    //修改已有的方法  
    public static void test03() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bqt.test.Person");

        CtMethod cm = cc.getDeclaredMethod("hello", new CtClass[] { pool.get("java.lang.String") });
        cm.insertBefore("System.out.println(\"調用前\");");//調用前
        cm.insertAt(20094, "System.out.println(\"在指定行插入代碼\");");//貌似行號胡亂寫也可以
        cm.insertAfter("System.out.println(\"調用後\");");//調用後

        CtMethod cm2 = cc.getDeclaredMethod("hello2", new CtClass[] { pool.get("java.lang.String") });
        cm2.setBody("{" + // 你只需要正常寫代碼邏輯就可以了,復制過來時,一些IDE,比如AS會自動幫你添加轉義字符
                "if ($1 == null) {\n" + //$0代表的是this,$1代表方法參數的第一個參數、$2代表方法參數的第二個參數
                "\treturn \"\";\n" + //
                "}\n" + //
                "return  \"你好:\" + $1;" + //
                "}"); //修改方法

        //通過反射調用方法  
        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method m = clazz.getDeclaredMethod("hello", String.class);
        System.out.println(m.invoke(obj, "張三"));

        Method m2 = clazz.getDeclaredMethod("hello2", String.class);
        System.out.println(m2.invoke(obj, "張三"));

        cc.writeFile("D:/test");//保存到指定位置
    }

    //添加屬性  
    public static void test04() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bqt.test.Person");

        CtField cf = new CtField(CtClass.intType, "age", cc);
        cf.setModifiers(Modifier.PRIVATE);
        cc.addField(cf);
        cc.addMethod(CtNewMethod.getter("getAge", cf));
        cc.addMethod(CtNewMethod.setter("setAge", cf));

        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Field field = clazz.getDeclaredField("age");
        System.out.println(field);

        Method m = clazz.getDeclaredMethod("setAge", int.class);
        m.invoke(obj, 16);
        Method m2 = clazz.getDeclaredMethod("getAge", null);
        Object resutl = m2.invoke(obj, null);
        System.out.println(resutl);

        cc.writeFile("D:/test");//保存到指定位置
    }

    //構造方法  
    public static void test05() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.bqt.test.Person");

        CtConstructor[] cons = cc.getConstructors();
        for (CtConstructor con : cons) {
            System.out.println(con.getLongName());
        }
    }
}

原始類

package com.bqt.test;

public class Person {

    public int hello(String s) {
        return s.length();
    }

    public String hello2(String s) {
        return s;
    }
}

test02

package com.bqt.test;

public class Person {
    public int hello(String s) {
        return s.length();
    }

    public String hello2(String s) {
        return s;
    }

    public int add1(int paramInt1, int paramInt2) {
        return paramInt1 + paramInt2;
    }

    public int add2(int paramInt1, int paramInt2) {
        return paramInt1 + paramInt2;
    }
}

test03

package com.bqt.test;

public class Person {
    public int hello(String s) {
        System.out.println("調用前");
        System.out.println("在指定行插入代碼");
        int i = s.length();
        System.out.println("調用後");
        return i;
    }

    public String hello2(String paramString) {
        if (paramString == null) {
            return "";
        }
        return "你好:" + paramString;
    }
}

test04

package com.bqt.test;

public class Person {
    private int age;

    public int hello(String s) {
        return s.length();
    }

    public String hello2(String s) {
        return s;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int paramInt) {
        this.age = paramInt;
    }
}

2019-1-7

Javassist 字節碼 反編譯 語法 案例 MD