Javassist之字节码读写

Javassist是一个用于处理Java字节码的类库。Java字节码是一个以二进制文件进行存储的class文件。每一个class文件都包含一个Javal类或者是接口。

Javassist.Ctclass是一个对class文件的一个抽象表示形式。一个CtClass(编译时类)对象是一个处理class文件的句柄。下面这段程序是一个非常简单的例子:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();

这段程序首先获取一个ClassPool对象,它控制着Javassist对字节码的修改。ClassPool对象是一个表示class文件的CtClass对象的容器。它读取一个类文件,用于构造CtClass对象,并记录构建的对象以响应后面的访问。

要去改变一个类的定义,开发者必须要先从ClassPool对象中,获得一个表示该类的CtClass对象的引用。该类可以通过ClassPool的get()方法获取。在上面的展示的程序示例中,调用ClassPool的get()方法,返回了一个用于表示test.Rectangle类的CtClass对象cc。ClassPool.getDefault()方法的作用是扫描默认系统路径的类,返回一个ClassPool对象。

站在源码的角度,ClassPool是一个使用类的名称当作key,对应的CtClass为value的hash表。ClassPool的get()方法根据传入的类名参数,在hash表中查询到该类名对应的CtClass对象并返回。如果没有查询到该类名对应的CtClass对象,get()方法将会读取类文件来构造一个新的CtClass对象,这个对象会被添加到hash表中,然后返回。

从ClassPool对象中获取的CtClass对象是可以被改变的(关于如何修改CtClass的详细说明将会在下文展示)。在上面的例子中,test.Rectangle的父类被改变成了test.Point。这个改变在最后调用CtClass()的writeFile()方法时会被写入源class文件。

writeFile()方法将CtClass对象转变成一个class文件,并写入本地磁盘。Javassist也提供了直接获取修改过的字节码的方法。调用toBytecode()方法,直接获取字节码:

byte[] b = cc.toBytecode();

也可以直接读取CtClass:

Class clazz = cc.toClass();

toClass()方法会请求当前线程的上下文类加载器去加载CtClass表示的类文件。它返回一个表示被加载类的java.lang.Class对象。更加详细的说明,请看下面的部分。

定义一个新的类

要重新定义一个类,必须调用ClassPool的makeClass()方法。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");

这段代码定义了一个Point类,它没有包含任何的属性或方法。Point类可以通过调用CtNewMethod的工厂方法来创建方法,并使用CtClass的addMethod()将该方法添加到Point类中。

makeClass()无法创建一个新的接口,但ClassPool的makeInterface()方法可以。接口的方法可以通过CtNewMethod的abstractMethod()方法创建。注意,一个接口方法是一个抽象的方法。

类的冻结

如果一个CtClass对象通过writeFile(),toClass(),或者toBytecode()转换到一个类文件,Javassist将会冻结CtClass对象。对CtClass对象的进一步改变将不被允许。因为JVM不会允许重新加载一个类,所以当开发者尝试去改变一个已经加载的类时,将会发出警告。

一个冻结的CtClass可以被解除,解冻之后就可以允许对类的定义进行修改。例如下面这个例子:

CtClasss cc = ...;
    :
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // OK since the class is not frozen.

在调用defrost()方法之后,CtClass对象就可以再次被修改。

如果ClassPool.doPruning被设置成true,在Javassist冻结CtClass对象的时候,Javassist将会对CtClass对象包含的数据结构进行精简。通过修剪丢弃对象不需要的属性(attribute_info结构)来减少内存的消耗。例如,丢弃Code_attribute结构(方法体)。因此,当CtClass对象被精简之后,方法的字节码除了方法名,签名,以及注解之外将不能被访问。精简的CtClass对象无法再次被解冻。ClassPool.doPruning的默认值是false。

对于一些并不想被精简的特殊CtClass,必须提前调用stopPruning():

CtClasss cc = ...;
cc.stopPruning(true);
    :
cc.writeFile();                             // convert to a class file.
// cc is not pruned.

CtClass对象cc没有被精简。因此它可以在writeFile()被调用之后解冻。

注意:在进行debug的时候,可能需要暂时停止精简或者冻结或者将修改过的类文件写入磁盘。debugWriteFile()方法可以方便的实现此功能。它可以停止精简,写类文件,解冻,同时再次精简。

类扫描路径

由静态方法ClassPool.getDefault()返回的默认ClassPool与JVM有相同的扫描路径。如果程序在JBoss或者Tomcat之类的web应用程序中,ClassPoll对象可能无法找到用户的类,因为这些Web应用服务器使用多个类加载器以及系统类加载器。在这种情况下,就必须注册额外的类扫描路径到ClassPool中。可以使用如下方式进行注册:

pool.insertClassPath(new ClassClassPath(this.getClass()));

这行代码注册了一类路径,它被用来加载this所指代的对象的类。this.getClass()参数可以被替换成任何一个类对象。用于加载由已经注册的该类对象表示的类的类路径。

你可以注册一个目录名称来作为类扫描路径。例如,下面的代码将/usr/local/javalib添加到类扫描路径:

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

用户可以添加的扫描路径不仅仅是目录,也可以是一个URL:

ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

这段代码将"http://www.javassist.org:80/java/"添加到类扫描路径。这个URL仅仅被用来扫描属于org.javassist包的类。例如,想要加载一个org.javassist.test.Main,它的类文件可以这样获得:

http://www.javassist.org:80/java/org/javassist/test/Main.class

此外,你可以直接传入一个字节数组到ClassPool对象中,由这个数组构造出一个CtClass对象。可以使用ByteArrayClassPath来做到。例如:

ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);

所获得的CtClass对象表示由b所引用的类文件定义的类。如果调用了get()方法,ClassPool会从给定的ByteArrayClassPath中读取一个类文件,传入给get()的类名参数与所指定的类名相同。

如果你不知道完整的类名,那么你可以使用ClassPool的makeClass():

ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

makeClass()方法返回一个由所给的输入流构造的CtClass对象。你可以使用makeClass()将类文件直接传入到ClassPool对象中。如果扫描路径包含很大的jar文件,这可能会提升性能。因为ClassPool对象只会在需要的时候才会读取类文件,所以它可能会因为每一个类文件而重复扫描整个jar文件。makeClass()可以被用来优化重复扫描的情况。由makeClass()构造的CtClass将一直被ClassPool对象所持有,所对应的类文件也将不会再被读取。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,977评论 25 707
  • 不管在上学还是在工作,相信每个人都会遇到这样的状况~身边的亲戚朋友包括父母,都会打着“为你好”的名义对你所做...
    一梦千年花已落阅读 330评论 0 1
  • 每个人的心里,都有自己的一个大鱼海棠。 我叫海棠,从小在大渔村里长大。 大渔村是大泽边上的一个小村庄,这里几乎与世...
    廿八粥阅读 446评论 0 0
  • 关于wkhtmltox,是一个可以把HTML转换为图片和pdf的工具。 不多介绍了,详见官网 https://wk...
    Joepis阅读 10,723评论 0 14