Javassist之Classloader(二)

Javassist之Classloader(一)中我們講述了Javassist的toClass()以及Java的類加載器,本次我們將介紹Javassist的加載器,以及自定義加載器。

1. 使用javassist.Loader

Javassist提供了一個javassist.loader類加載器。這個類加載器使用一個javassist.ClassPool對象來讀取類文件。

例如,javassist.Loader可以被用來讀取被Javassist修改的特殊類。

import javassist.*;
import test.Rectangle;
public class Main {
public static void main(String[] args) throws Throwable {
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader(pool);


 CtClass ct = pool.get("test.Rectangle");
 ct.setSuperclass(pool.get("test.Point"));

 Class c = cl.loadClass("test.Rectangle");
 Object rect = c.newInstance();
     :

}
}

這段程序修改了一個test.Rectangle類。test.Rectangle的父類被設置為test.Point類。然后加載了修改后的類,同時創(chuàng)建了一個test.Rectangle類的新實例。

如果用戶想要在類被加載時按需修改的話,用戶可以添加一個javassist.Loader的事件監(jiān)聽。添加事件監(jiān)聽器在類加載器加載類時會被通知。事件監(jiān)聽類必須實現(xiàn)下面的接口:

public interface Translator {
    public void start(ClassPool pool)
        throws NotFoundException, CannotCompileException;
    public void onLoad(ClassPool pool, String classname)
        throws NotFoundException, CannotCompileException;
}

通過javassist.Loader的addTranslator()將事件監(jiān)聽器添加到javassist.Loader中時,方法start()會被調(diào)用。方法onLoad()在javassist.Loader加載類之前調(diào)用。onLoad()可以修改加載類的定義。

例如,下面的事件監(jiān)聽器在所有類被加載之前,修改所有的類為公共類。

public class MyTranslator implements Translator {
    void start(ClassPool pool)
        throws NotFoundException, CannotCompileException {}
    void onLoad(ClassPool pool, String classname)
        throws NotFoundException, CannotCompileException
    {
        CtClass cc = pool.get(classname);
        cc.setModifiers(Modifier.PUBLIC);
    }
}

注意onLoad()不必去調(diào)用toBytecode()或者writeFile(),因為javassist.Loader調(diào)用了這些方法來獲取類文件。

為了通過MyTranslator對象運行MyApp應用,編寫如下主函數(shù):

import javassist.*;
public class Main2 {
public static void main(String[] args) throws Throwable {
Translator t = new MyTranslator();
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader();
cl.addTranslator(pool, t);
cl.run("MyApp", args);
}
}

為了運行這段程序,如下:

% java Main2 arg1 arg2...

MyApp類和其它應用類被MyTranslator翻譯。

注意如MyApp的應用類不能訪問例如Main2,MyTranslator和ClassPool的類加載器,因為它們被不同的類加載器加載。應用類被javassist.Loader加載,反之Main2等類是被默認的Java類加載器加載的。

javassist.Loader與java.lang.ClassLoader按不同的順序掃描類。ClassLoader首先委托加載行為給父加載器,它只會加載父加載器無法加載的類。另一方面,javassist.Loader試圖在委托給父加載器之前加載類。它只有在下面的情況才會進行委托:

  1. 調(diào)用ClassPool對象的get()并不能發(fā)現(xiàn)的類
  2. 使用delegateLoadingOf()被指定由父加載器加載的類

這個掃描順序允許通過Javassist加載修改類。然而,它因為某些原因無法加載修改類時,它會委托給父類加載。一旦一個類被父類加載,其它的被該類引用的類也會被父加載器加載,因此它們從不被修改。所有在類C中所引用的類都被類C的實際加載器加載。如果你的程序加載修改類失敗,你應該確認是否所有類被修改類引用的類都已經(jīng)被javassist.Loader加載。

2. 寫一個類加載

一個簡單的使用Javassist的類加載器如下:

import javassist.*;
public class SampleLoader extends ClassLoader {
/* Call MyApp.main().
*/
public static void main(String[] args) throws Throwable {
SampleLoader s = new SampleLoader();
Class c = s.loadClass("MyApp");
c.getDeclaredMethod("main", new Class[] { String[].class })
.invoke(null, new Object[] { args });
}


private ClassPool pool;

public SampleLoader() throws NotFoundException {
    pool = new ClassPool();
    pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em>
}

/* Finds a specified class.
 * The bytecode for that class can be modified.
 */
protected Class findClass(String name) throws ClassNotFoundException {
    try {
        CtClass cc = pool.get(name);
        // <em>modify the CtClass object here</em>
        byte[] b = cc.toBytecode();
        return defineClass(name, b, 0, b.length);
    } catch (NotFoundException e) {
        throw new ClassNotFoundException();
    } catch (IOException e) {
        throw new ClassNotFoundException();
    } catch (CannotCompileException e) {
        throw new ClassNotFoundException();
    }
}

}

類MyApp是一個應用程序,為了執(zhí)行這個程序,首先把類文件放到./class目錄下,它不能包含在掃描路徑中。否則,MyApp.class會被默認系統(tǒng)類加載器加載,它是SampleLoader的父類。目錄名./class是在構(gòu)造方法里的insertClassPath()指定。如果需要,你可以選擇一個和./class不同的目錄。然后做如下操作:

% java SampleLoader

類加載器會加載MyApp(./class/MyApp.class)同時使用命令行參數(shù)調(diào)用MyApp.main()。

這是使用Javassist最簡單的方式。然而,如果你寫一個更復雜的類加載器,你可能需要更詳細的關于Java類加載器機制的知識。例如,上面的程序?qū)袽yApp類放在與SampleLoader不同的命名空間中,因為兩個類被不同的類加載器加載。因此,MyApp類無法直接訪問類SampleLoader。

3. 修改系統(tǒng)類

如java.lang.String的系統(tǒng)類不能被除了系統(tǒng)類加載器之外的類加載加載。因此,上面展示的SampleLoader或者javassist.Loader無法在加載時修改系統(tǒng)類。

如果你的應用需要,系統(tǒng)類必須靜態(tài)修改。例如,下面的程序添加了一個新的屬性hiddenValue到java.lang.String:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("java.lang.String");
CtField f = new CtField(CtClass.intType, "hiddenValue", cc);
f.setModifiers(Modifier.PUBLIC);
cc.addField(f);
cc.writeFile(".");

這歌程序生成了一個“./java/lang/String.class”文件。

做以下操作使用修改的String類運行你的MyApp程序:

% java -Xbootclasspath/p:. MyApp arg1 arg2...

假設MyApp的定義如下:

public class MyApp {
    public static void main(String[] args) throws Exception {
        System.out.println(String.class.getField("hiddenValue").getName());
    }
}

如果修改的String類被正確加載,MyApp打印hiddenValue。

注意:程序使用這種技術復寫的rt.jar的系統(tǒng)類不應該被發(fā)布,因為這樣做會違反Java 2 Runtime Environment二進制代碼許可證。

4. 運行時重新加載類

如果JVM使用JPDA(Java Platform Debugger Architecture)開啟的模式運行,一個類可以被動態(tài)重新加載。在JVM加載類之后,舊版本的類定義可以被卸載,新的可以被再次加載。這意味著,類的定義可以在運行器被動態(tài)加載。然而,新的類定義必須與舊的兼容。JVM不允許兩個版本之間的不兼容。它們需要擁有相同的方法和屬性。

Javassist提供了一個方便的類用于在運行期間重新加載類。更多的信息,可以查看javassist.tools.HotSwapper文檔的API。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • 本文譯自: Javassist Tutorial-1原作者: Shigeru Chiba完成時間:2016年11月...
    二胡閱讀 52,109評論 9 54
  • 1 基本信息 每個開發(fā)人員對java.lang.ClassNotFoundExcetpion這個異??隙ǘ疾荒吧?..
    java小菜鳥閱讀 2,688評論 0 15
  • 2018.5.17,廣州,雨 廣州的天氣一下雨就特別的悶熱,要么悶熱的要下雨,現(xiàn)在春天和秋天越來越奢侈了。 烏云籠...
    紅花姐閱讀 411評論 0 0
  • 陪在身邊,才是擁有;愛到習慣,才會長久。 我和時先生相遇在萬物清朗的夏季,沒有驚天動地的奇遇,沒有婉轉(zhuǎn)動人的情節(jié),...
    蒔惜閱讀 332評論 0 1

友情鏈接更多精彩內(nèi)容