Javassist 是一個強大的 Java 字節碼操作庫,最大特色是允許開發者以 Java 原始碼字串的形式編輯方法體,無需深入理解 JVM 指令集。它是 JBoss 應用服務器 AOP 功能的基礎。本文將介紹其核心 API、基本操作流程,並透過「方法計時」實例展示如何在無原始碼情況下修改類行為。
.class 文件的搜尋路徑,提供 get() 取得 CtClass。java.lang.Class,但支援增刪欄位、方法、構造器、修改繼承關係等。Javassist 接受單條語句或代碼塊(用 {...} 包裹)作為新方法體。特殊標識符以 $ 開頭:
$1, $2...:方法參數$$:所有參數列表$0:this 參考$_:返回值(僅在方法尾部插入時有效)限制:插入的代碼塊無法直接引用外部局部變量,需透過欄位或攔截器模式傳遞狀態。
public class StringBuilder {
private String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char)(i % 26 + 'a');
}
return result;
}
public static void main(String[] argv) { /* ... */ }
}
因 Javassist 不允許跨代碼塊共享局部變量,採用「重命名原方法 + 新建同名攔截器」策略:
private String buildString$impl(int length) { /* 原邏輯 */ }
private String buildString(int length) {
long start = System.currentTimeMillis();
String result = buildString$impl(length);
System.out.println("Call took " + (System.currentTimeMillis()-start) + " ms.");
return result;
}
public class JassistTiming {
public static void main(String[] argv) {
CtClass clas = ClassPool.getDefault().get(argv[0]);
addTiming(clas, argv[1]);
clas.writeFile(); // 寫回磁碟
}
private static void addTiming(CtClass clas, String mname)
throws NotFoundException, CannotCompileException {
CtMethod mold = clas.getDeclaredMethod(mname);
String nname = mname + "$impl";
mold.setName(nname); // 重命名
CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null); // 複製為新同名方法
// 構建攔截器方法體
String type = mold.getReturnType().getName();
StringBuffer body = new StringBuffer();
body.append("{\nlong start = System.currentTimeMillis();\n");
if (!"void".equals(type)) body.append(type + " result = ");
body.append(nname + "($$);\n");
body.append("System.out.println(\"Call to method " + mname + " took \" +\n");
body.append("(System.currentTimeMillis()-start) + \" ms.\");\n");
if (!"void".equals(type)) body.append("return result;\n");
body.append("}");
mnew.setBody(body.toString());
clas.addMethod(mnew);
}
}
修改後執行 java StringBuilder 1000 2000 4000 8000 16000,輸出顯示計時資訊,且耗時隨長度呈非線性增長,驗證了字符串拼接的低效特性。
Javassist 編譯較寬鬆,可能生成語義錯誤的字節碼:
long 賦給 int 不報錯,導致時間溢位顯示錯誤值。String 當 int)會產生通過編譯但運行時 VerifyError 的類。開發者需自行嚴謹檢查傳入的源碼字串。
Javassist 以源碼字串操作字節碼,大幅降低門檻,適合快速原型與 AOP 織入。下一部分將探討其動態載入時轉換與批量修改能力。