首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Javassist 進階:用 ExprEditor 實現 AOP 風格的字節碼全局搜索替換

     編輯:Java 相關知識

Javassist 不僅支持單點修改,更提供 CodeConverter(簡單但受限)與 ExprEditor(強大靈活)兩套系統級字節碼轉換機制。本文深入演示:用 CodeConverter.insertBeforeMethod() 監控特定 Setter → 受限於簽名匹配;改用 ExprEditor 實現字段寫入攔截並替換值(如反轉字符串)、方法調用攔截、新對象創建攔截,並展示 VerboseEditor 全量剖析類結構的調試技巧。最後介紹 JBoss AOP 基於 Javassist 的實踐。

CodeConverter:簡易但受限

配置轉換規則 → 應用於所有加載類。示例:攔截 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:字節碼級的「替換全部」

繼承 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 實現類加載期自動織入。

對比總結

特性CodeConverterExprEditor
API 簡單度高中
簽名匹配要求嚴格無(源碼片段動態編譯)
可攔截點方法調用/字段/新對象同上 + 類型轉換/instanceof/catch
條件過濾僅類名任意邏輯(字段名、調用者等)
適用場景固定監控點通用 AOP、框架開發

生態延伸:JBoss AOP

JBoss AOP 以 XML 定義攔截器、引入接口等切面,底層即基於 Javassist 的 ExprEditor 機制。已集成至 JBoss AS,亦可獨立使用。

下一站:BCEL

下文將介紹 Apache BCEL,從指令級別操作字節碼,提供比 Javassist 更底層的控制力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved