javassist翻译之一《读写字节码》

Javassist是一个处理Java字节码的类库。 Java字节码存储在类的二进制文件中。 每个类文件都包含一个Java类或接口。

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

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

示例中首先获得一个ClassPool对象,它通过Javassist控制字节码的修改。ClassPool对象是一个代表类文件的CtClass对象的容器,它根据需要读取一个类文件来构造CtClass对象,并保存构造过的对象以响应以后的访问。如果想要修改一个类的定义,用户首先必须从ClassPool对象获得一个对表示该类的CtClass对象的引用,我们可以使用ClassPool中的get()方法来获得这个类的引用。在上面的例子中,类“test.Rectangle”的CtClass对象时从ClassPool对象中获得的,并且将类“test.Rectangle”的CtClass对象赋值给CtClass类型的变量cc。ClassPool的对象通过ClassPool的静态方法getDefault()返回获得的,getDefault()方法搜索系统默认的搜索路径。

从实现角度来看,ClassPool是一个用类名作为key的存储CtClass对象的哈希表。ClassPool中的get()方法通过指定的key来找到映射的CtClass对象。如果没有找到key映射的CtClass对象,get()会读取类库中名为key的类文件来构造一个新的CtClass对象,并将该对象存储在哈希表(ClassPool)中,然后作为get()的结果值返回。

从ClassPool对象中获取的CtClass对象可以被修改,具体怎么修改后面会进行介绍。在上面的例子中,将”test.Point”类设置为了”test.Rectangle”的父类。当CtClass()中的writeFile()方法最终被调用时,这个改变反映在原来的类文件上。

writeFile()方法将CtClass对象转换为一个类文件并将其写入本地磁盘。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()方法来创建。请注意,接口中的方法是一种抽象方法。


冻结的类

如果通过writeFile( ),toClass( )或toBytecode( )将CtClass对象转换为类文件,javassist将会冻结这个CtClass对象。此时想要进一步修改这个CtClass对象是不被允许的。这是为了警告开发者当他们试图修改一个已经被加载的类文件,因为JVM不允许重复加载一个类。

一个被冻结的CtClass可以被解冻,也就意味着你可以重新修改这个CtClass。例如:

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

当CtClass的defrost( )方法被调用后,这个CtClass对象就可以被再次修改了。

如果ClassPool.doPruning设置为true,那么当Javassist冻结该对象时,Javassist会修剪CtClass对象中包含的数据结构。为了减少内存消耗,修剪会丢弃该对象中不必要的属性(属性信息结构)。例如,代码属性结构(方法体)将会被被丢弃。因此,在对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( )被调用后解冻。

注:在调试时,您可能需要暂时停止修剪和冻结,并将已修改的类文件写入磁盘驱动器。 debugWriteFile()是一个方便的方法。 它停止修剪,写入一个类文件,解冻它,然后再次修剪(如果它是最初的话)。


类搜索路径

由静态方法ClassPool.getDefault( )返回的ClassPool默认搜索底层JVM(Java虚拟机)具有的相同路径。如果程序在Web应用程序服务器上运行,比如JBoss和Tomcat,那么ClassPool对象可能无法找到用户类,因为这样的Web应用程序服务器使用了多个类加载器以及系统类加载器。在这种情况下,一个额外的类路径必须注册到ClassPool。假设’pool‘引用了一个ClassPool对象:

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

此语句注册用于加载所引用的对象的类的类路径。你可以使用让我任意Class对象或一个参数替代this.getClass( ). The class path used for loading the class represented by that Classobject is registered.

您可以注册一个目录名称作为类搜索路径。例如,将目录 /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对象:(看不懂。。。。。。)

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( )被调用且传给get( )的’name‘等于ByteArrayClassPath中指定的’name‘,则ClassPool将从给定的ByteArrayClassPath中读取一个类文件。

如果您不知道该类的完全限定名称,则可以使用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对象中,并且构造CtClass对象的那个文件不会被再次读取。

用户可以扩展类搜索路径。 他们可以定义一个实现ClassPath接口的新类,并将这个类的实例插入ClassPool的ClassPath( )方法中。这允许在搜索路径中包含非标准资源。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值