Javassist 不僅支持單點修改,更提供 CodeConverter(簡單但受限)與 ExprEditor(強大靈活)兩套系統級字節碼轉換機制。本文深入演示:用 CodeConverter.insertBeforeMethod() 監控特定 Setter → 受限於簽名匹配;改用 ExprEditor 實現字段寫入攔截並替換值(如反轉字符串)、方法調用攔截、新對象創建攔截,並展示 VerboseEditor 全量剖析類結構的調試技巧。最後介紹 JBoss AOP 基於 Javassist 的實踐。
配置轉換規則 → 應用於所有加載類。示例:攔截 Bean.setA(String),前置調用 TranslateConvert.reportSet(Bean, String)。
CodeConverter convert = new CodeConverter();
CtMethod smeth = pool.get("Bean").getDeclaredMethod("setA");
CtMethod pmeth = pool.get("TranslateConvert").getDeclaredMethod("reportSet");
convert.insertBeforeMethod(smeth, pmeth); // 要求 reportSet 簽名完全匹配
缺陷:監控方法必須 static void,第一參數為目標類型,其餘與目標方法一致。泛化極難(如改為 Object 即報 NoSuchMethodError)。
繼承 ExprEditor,重寫三個 edit() 重載:
edit(FieldAccess):字段讀/寫edit(MethodCall):方法調用edit(NewExpr):new 表達式每個 edit() 參數提供 replace(String sourceCode),傳入 Javassist 源碼片段即可替換原字節碼。
public class VerboseEditor extends ExprEditor {
public void edit(FieldAccess fa) {
System.out.println((fa.isReader()?"read":"write") + " of " + fa.getClassName()+"."+fa.getFieldName() + location(fa));
}
public void edit(MethodCall mc) {
System.out.println("call to " + mc.getClassName()+"."+mc.getMethodName() + location(mc));
}
public void edit(NewExpr ne) {
System.out.println("new " + ne.getClassName() + location(ne));
}
private String location(Expr e) { ... } // 文件名:行號
}
運行輸出示例(節選):
Dissecting class BeanTest
new Bean in BeanTest(BeanTest.java:7)
write of BeanTest.m_bean in BeanTest(BeanTest.java:7)
call to Bean.setA in changeValues(BeanTest.java:16)
write of Bean.m_a in Bean(Bean.java:23)
...
public class FieldSetEditor extends ExprEditor {
private String targetField;
public FieldSetEditor(String f) { targetField = f; }
public void edit(FieldAccess fa) {
if (fa.getFieldName().equals(targetField) && fa.isWriter()) {
fa.replace("$0." + targetField + " = TranslateEditor.reverse($1);");
}
}
}
TranslateEditor.reverse(String) 返回反轉字符串並打印日誌。運行效果:
TranslateEditor.reverse returning Alanigiro
Bean values are Alanigiro and originalB
TranslateEditor.reverse returning Awen
Bean values are Awen and newB
僅幾行代碼實現了對所有賦值點的橫切修改,無需修改業務代碼。
實現 Translator 接口,在 onWrite(ClassPool, className) 中調用 CtClass.instrument(exprEditor),配合 ClassPool.getDefault(translator) 與 Loader 實現類加載期自動織入。
| 特性 | CodeConverter | ExprEditor |
|---|---|---|
| API 簡單度 | 高 | 中 |
| 簽名匹配要求 | 嚴格 | 無(源碼片段動態編譯) |
| 可攔截點 | 方法調用/字段/新對象 | 同上 + 類型轉換/instanceof/catch |
| 條件過濾 | 僅類名 | 任意邏輯(字段名、調用者等) |
| 適用場景 | 固定監控點 | 通用 AOP、框架開發 |
JBoss AOP 以 XML 定義攔截器、引入接口等切面,底層即基於 Javassist 的 ExprEditor 機制。已集成至 JBoss AS,亦可獨立使用。
下文將介紹 Apache BCEL,從指令級別操作字節碼,提供比 Javassist 更底層的控制力。