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

Java 動態編程第 5 部分:運用 Javassist 實現類的動態轉換與載入時織入

     編輯:Java 相關知識

前言:從靜態修改到動態攔截

在本系列前幾篇文章中,我們已探討如何使用 Javassist 修改編譯後的類文件並將其寫回磁碟。這種靜態轉換適合永久性變更,但若需在每次應用啟動時套用不同的修改,則顯得不夠靈活。本文將展示如何利用 JVM 的類載入機制,結合 Javassist 實現「載入時織入」,使類在被載入 JVM 之前即可被動態轉換,從而支援面向切面編程(AOP)風格的即時行為修改。

類載入的攔截點:自訂 ClassLoader

JVM 透過類載入器層級結構載入類,每個載入器(除啟動類載入器外)皆有父載入器,並遵循「雙親委派模型」:優先請求父載入器載入,失敗才嘗試自行載入。要攔截類載入,需提供自訂 ClassLoader 並將其置於系統類載入器之下,使其成為應用類的實際載入者。

示例:VerboseLoader — 可視化類載入過程

以下代碼繼承 java.net.URLClassLoader,重寫 loadClass() 與 findClass() 方法,打印每個類的載入請求與實際載入來源:

public class VerboseLoader extends URLClassLoader {
    protected VerboseLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public Class loadClass(String name) throws ClassNotFoundException {
        System.out.println("loadClass: " + name);
        return super.loadClass(name);
    }

    protected Class findClass(String name) throws ClassNotFoundException {
        Class clas = super.findClass(name);
        System.out.println("findClass: loaded " + name + " from this loader");
        return clas;
    }

    public static void main(String[] args) { /* ...啟動邏輯... */ }
}

執行 java VerboseLoader Run 可見只有 Run 類由自訂載入器載入,核心類皆委派給父載入器。

Javassist 的 Loader 與 Translator 機制

Javassist 內建 javassist.Loader 類,封裝了自訂類載入器的建立與啟動流程。核心在於 javassist.ClassPool 採用觀察者模式:向其註冊 javassist.Translator 實例,當類被請求時,onWrite() 回調允許在類定義交付前修改其字節碼。

簡易運行器:JavassistRun

public class JavassistRun {
    public static void main(String[] args) {
        Translator xlat = new VerboseTranslator();
        ClassPool pool = ClassPool.getDefault(xlat);
        Loader loader = new Loader(pool);
        String[] pargs = Arrays.copyOfRange(args, 1, args.length);
        loader.run(args[0], pargs);
    }

    static class VerboseTranslator implements Translator {
        public void start(ClassPool pool) {}
        public void onWrite(ClassPool pool, String cname) {
            System.out.println("onWrite called for " + cname);
        }
    }
}

輸出顯示 onWrite called for Run,證明攔截生效。

實戰:載入時添加方法計時代碼

承接第 4 部分的計時範例,我們將靜態字節碼修改轉為載入時動態植入。關鍵在於 Translator.onWrite() 中比對目標類名,匹配則取得 CtClass 並注入計時邏輯。

核心邏輯:SimpleTranslator

public static class SimpleTranslator implements Translator {
    private String m_className, m_methodName;

    public SimpleTranslator(String cname, String mname) {
        m_className = cname; m_methodName = mname;
    }

    public void start(ClassPool pool) {}
    public void onWrite(ClassPool pool, String cname)
            throws NotFoundException, CannotCompileException {
        if (cname.equals(m_className)) {
            CtClass clas = pool.get(cname);
            addTiming(clas, m_methodName); // 參見第 4 部分實現
        }
    }
}

啟動方式:java TranslateTiming 目標類 目標方法 主類 參數...

進階:基於模式匹配的批量計時

為支援更靈活的切點選擇,設計一套類似 Java 方法聲明的模式語法,支援返回類型、類/方法名通配符、參數列表精確或無序匹配。例如:

  • java.lang.String test.StringBuilder.buildString(int)
  • *buildString(int, java.lang.String) — 精確順序
  • *buildString [int,java.lang.String] — 無序且可有額外參數

TimingTranslator 在 onWrite() 中遍歷類的所有方法,依模式匹配返回類型、參數、名稱,符合者植入計時碼。完整匹配邏輯見附件下載。

總結與展望

本文示範了如何結合 Javassist 的 ClassPool、Loader、Translator 實現載入時類轉換,並發展出模式驅動的批量計時工具。下一部分將深入 Javassist 的字節碼搜尋/替換高階特性,進一步揭示其作為 AOP 框架的核心優勢。

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