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类。然后加载了修改后的类,同时创建了一个test.Rectangle类的新实例。

如果用户想要在类被加载时按需修改的话,用户可以添加一个javassist.Loader的事件监听。添加事件监听器在类加载器加载类时会被通知。事件监听类必须实现下面的接口:

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

通过javassist.Loader的addTranslator()将事件监听器添加到javassist.Loader中时,方法start()会被调用。方法onLoad()在javassist.Loader加载类之前调用。onLoad()可以修改加载类的定义。

例如,下面的事件监听器在所有类被加载之前,修改所有的类为公共类。

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()不必去调用toBytecode()或者writeFile(),因为javassist.Loader调用了这些方法来获取类文件。

为了通过MyTranslator对象运行MyApp应用,编写如下主函数:

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. 调用ClassPool对象的get()并不能发现的类
  2. 使用delegateLoadingOf()被指定由父加载器加载的类

这个扫描顺序允许通过Javassist加载修改类。然而,它因为某些原因无法加载修改类时,它会委托给父类加载。一旦一个类被父类加载,其它的被该类引用的类也会被父加载器加载,因此它们从不被修改。所有在类C中所引用的类都被类C的实际加载器加载。如果你的程序加载修改类失败,你应该确认是否所有类被修改类引用的类都已经被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是一个应用程序,为了执行这个程序,首先把类文件放到./class目录下,它不能包含在扫描路径中。否则,MyApp.class会被默认系统类加载器加载,它是SampleLoader的父类。目录名./class是在构造方法里的insertClassPath()指定。如果需要,你可以选择一个和./class不同的目录。然后做如下操作:

% java SampleLoader

类加载器会加载MyApp(./class/MyApp.class)同时使用命令行参数调用MyApp.main()。

这是使用Javassist最简单的方式。然而,如果你写一个更复杂的类加载器,你可能需要更详细的关于Java类加载器机制的知识。例如,上面的程序将把MyApp类放在与SampleLoader不同的命名空间中,因为两个类被不同的类加载器加载。因此,MyApp类无法直接访问类SampleLoader。

3. 修改系统类

如java.lang.String的系统类不能被除了系统类加载器之外的类加载加载。因此,上面展示的SampleLoader或者javassist.Loader无法在加载时修改系统类。

如果你的应用需要,系统类必须静态修改。例如,下面的程序添加了一个新的属性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的系统类不应该被发布,因为这样做会违反Java 2 Runtime Environment二进制代码许可证。

4. 运行时重新加载类

如果JVM使用JPDA(Java Platform Debugger Architecture)开启的模式运行,一个类可以被动态重新加载。在JVM加载类之后,旧版本的类定义可以被卸载,新的可以被再次加载。这意味着,类的定义可以在运行器被动态加载。然而,新的类定义必须与旧的兼容。JVM不允许两个版本之间的不兼容。它们需要拥有相同的方法和属性。

Javassist提供了一个方便的类用于在运行期间重新加载类。更多的信息,可以查看javassist.tools.HotSwapper文档的API。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 本文译自: Javassist Tutorial-1原作者: Shigeru Chiba完成时间:2016年11月...
    二胡阅读 51,654评论 9 54
  • 1 基本信息 每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,...
    java小菜鸟阅读 2,609评论 0 15
  • 2018.5.17,广州,雨 广州的天气一下雨就特别的闷热,要么闷热的要下雨,现在春天和秋天越来越奢侈了。 乌云笼...
    红花姐阅读 377评论 0 0
  • 陪在身边,才是拥有;爱到习惯,才会长久。 我和时先生相遇在万物清朗的夏季,没有惊天动地的奇遇,没有婉转动人的情节,...
    莳惜阅读 291评论 0 1