Javassist (位元組碼修改工具)用法
阿新 • • 發佈:2019-02-01
Javassist簡介
javassist是一個開源的分析、編輯和建立Java位元組碼的類庫。可以對.class進行直接修改和建立,直接使用java程式設計的方式,不需要了解虛擬機器的指令,就能動態的改變類的結構或者動態生成
Javassist 應用場景
應用效能的監控、動態代理等
重要類簡介:
- ClassPool:javassist的類池,使用ClassPool 類可以跟蹤和控制所操作的類,它的工作方式與 JVM 類裝載器非常相似。
使用方法:
ClassPool pool = new ClassPool();
pool.appendSystemPath();
或者
ClassPool classPool = new ClassPool(); classPool.insertClassPath(new LoaderClassPath(loader));
- CtClass: CtClass提供了檢查類資料(如欄位和方法)以及在類中新增新欄位、方法和建構函式、以及改變類、父類和介面的方法。不過,Javassist 並未提供刪除類中欄位、方法或者建構函式的任何方法。
建立新類: pool.makeClass("com.ty.pojo.User");;
載入已有類:pool.get("com.ty.javaagent.UserServiceImpl")
- CtField:用來訪問域
- CtMethod :用來訪問方法
- CtConstructor:用來訪問構造器
- 生成並裝載Class toClass()或者 toBytecode()
語法:
符號 | |
$0,$1,$2.... | $0表示this,其他的表示實際的引數 |
$args | 引數陣列,相當於new Object[]{$1,$2,.....} |
$$ | 所有的引數 |
$r | 用於型別轉換表示返回值的型別 |
$w | 將基礎型別轉換為一個包裝型別,如Integer a = ($w)5; 表示5轉換為 Integer。 如果不是基本型別則什麼都不做 |
a) 不能引用 在方法中其他地方定義的區域性變數
b) 不會對型別進行強制檢查
c) 使用特殊的專案語法符號
示例
匯入javassist jar
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.18.1-GA</version> <scope>compile</scope> </dependency>
1.建立新類
@Test
// 建立User類
public void createUser() throws Exception {
ClassPool pool = new ClassPool();
pool.appendSystemPath();
// 定義類
CtClass userClass = pool.makeClass("com.ty.pojo.User");
// id屬性
CtField idField = new CtField(CtClass.longType, "id", userClass);
userClass.addField(idField);
// name 屬性
CtField nameField = new CtField(pool.get("java.lang.String"), "name", userClass);
userClass.addField(nameField);
CtMethod getMethod = CtNewMethod.make("public String getName(){return this.name;}", userClass);
CtMethod setMethod = CtNewMethod.make("public void setName(String name){this.name = name;}", userClass);
userClass.addMethod(setMethod);
userClass.addMethod(getMethod);
Class<?> clazz = userClass.toClass();
System.err.println("class:" + clazz.getName());
System.err.println("屬性列表----------");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getType() + ":" + field.getName());
}
System.err.println("方法列表------------------------");
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getReturnType() + "-" + method.getName() + "-"
+ Arrays.toString(method.getParameterTypes()));
}
}
結果:
屬性列表----------
long id
class java.lang.String name
------------------------------------------------
方法列表------------------------
class java.lang.String getName []
void setName [class java.lang.String]
2.修改方法,統計方法的執行時間
@Test
public void updateGetUserInfoMethod() throws Exception {
ClassPool pool = new ClassPool();
pool.appendSystemPath();
// 定義類
CtClass userServiceClass = pool.get("com.ty.javaagent.UserServiceImpl");
// 需要修改的方法
CtMethod method = userServiceClass.getDeclaredMethod("getUserInfo");
// 修改原有的方法
method.setName("getUserInfo$agent");
// 建立新的方法,複製原來的方法
CtMethod newMethod = CtNewMethod.copy(method, "getUserInfo", userServiceClass, null);
// 注入的程式碼
StringBuffer body = new StringBuffer();
body.append("{\nlong start = System.currentTimeMillis();\n");
// 呼叫原有程式碼,類似於method();($$)表示所有的引數
body.append("getUserInfo$agent($$);\n");
body.append("System.out.println(\" take \" +\n (System.currentTimeMillis()-start) + " + "\" ms.\");\n");
body.append("}");
newMethod.setBody(body.toString());
// 增加新方法
userServiceClass.addMethod(newMethod);
UserServiceImpl userServiceImpl = (UserServiceImpl) userServiceClass.toClass().newInstance();
userServiceImpl.getUserInfo();
}
結果:
user:1
take 2000 ms.