Javassist之ClassPool

ClassPool对象是CtClass对象集合的容器。一旦CtClass对象被创建,它就会被永远一直记录在ClassPool内。这是因为当编译器编译这个CtClass表示的类源代码之后,可能需要访问这个CtClass。

例如,假设一个新的getter()方法被添加到一个表示Point类的CtClass对象中。然后程序尝试编译调用Point中的getter()方法的源代码,并且用编译过的代码作为方法体,并添加到另一个Line类中。如果CtClass对象表示的Point类丢失了,编译器可能无法编译getter()方法的调用。注意,最初的类定义并不包括getter()方法。因此,要想正确的编译这个方法调用,ClassPool必须一直持有程序执行期间所有的CtClass实例。

避免内存溢出

如果大量的CtClass对象变得出奇的大(这极少发生,因为Javassist会尝试使用各种方法来减少内存消耗),ClassPool可能导致大量内存消耗。为了避免这个问题,你可以明确地从ClassPool中移除不需要的CtClass。如果你调用CtClass对象的detach()方法,这个CtClass对象将会从ClassPool中移除。例如,

CtClass cc = ... ; cc.writeFile(); cc.detach();

在CtClass对象的detach()被调用之后,你不能够再调用该CtClass对象的任何方法。然而,你可以调用ClassPool的get()方法来生成一个表示同一个类的新的CtClass实例。如果你调用get()方法,ClassPool会再次读取类文件并重新创建CtClass对象,然后由get()方法返回。

另一种情况是偶尔会将一个新的ClassPool替换掉旧的ClassPool并释放旧的ClassPool。如果旧的ClassPool被垃圾回收了,那么这个ClassPool所包含的CtClass对象集也会被回收。想创建一个新的ClassPool实例,执行下面这段代码:

ClassPool cp = new ClassPool(true); // if needed, append an extra search path by appendClassPath()

这创建了一个与ClassPool.getDefault()返回的默认ClassPool相同的ClassPool。注意,ClassPool.getDefault()是一个提供便利的单例工厂方法。它创建一个与上述相同的ClassPool对象,尽管它持有并服用一个单例的ClassPool对象。一个被getDefault()返回的ClassPool没有特殊的角色。getDefault()方法是一个便利的方法。

注意,new ClassPool(true)是一个方便的构造方法,它构造了一个ClassPool对象并将系统扫描路径添加到其中。调用这个构造方法与下面这段代码等同:

ClassPool cp = new ClassPool(); cp.appendSystemPath(); // or append another path by appendClassPath()

级联的ClassPool

如果程序在web应用服务器上运行,创建多个ClassPool实例可能就是必须的了;应该为每个classloader(i.e.container)创建一个ClassPool实例。程序应该使用ClassPool的一个构造方法而不是调用getDefault()方法来创建ClassPool。

多个ClassPool对象可以像java.lang.ClassLoader一样被级联。例如,

ClassPool parent = ClassPool.getDefault(); ClassPool child = new ClassPool(parent); child.insertClassPath("./classes");

如果child.get()被调用,子ClassPool首先会委托给父ClassPool。如果父ClassPool查询类文件失败,子ClassPool将会尝试查找在./classes目录下的类文件。

如果child.childFirstLookup是true,子ClassPool会尝试在委托给父ClassPool之前查找。例如,

ClassPool parent = ClassPool.getDefault(); ClassPool child = new ClassPool(parent); child.appendSystemPath(); // the same class path as the default one. child.childFirstLookup = true; // changes the behavior of the child.

通过改变类名去定义一个新类

新的类可以被定义成一个已经存在的类的拷贝。程序如下所示:

ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); cc.setName("Pair");

这段程序首先获取类Point的CtClass对象。然后它调用setName()来给这个CtClass对象设置新的名字Pair。在调用之后,所有被该CtClass对象所表示的类定义的类名都将从Point改变成Pair。其他的类定义不回受到影响。

注意,CtClass的setName()方法改变了ClassPool对象里的一个记录。站在实现者的角度,一个ClassPool对象是一个CtClass对象的hash表。setName()改变了在与之相关的CtClass对象在hash表中的key值。key从最初的类名改为新的类名。

因此,如果之后再次调用ClassPool对象的get(“Point”)方法,它将永远不会返回便利cc所引用的CtClass对象。ClassPool再次读取Point.class,同时为它创建一个新的CtClass对象。这是因为与Point类名关联的CtClass已经不存在了。如这段代码:

ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); CtClass cc1 = pool.get("Point"); // cc1 is identical to cc. cc.setName("Pair"); CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc. CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.

cc1和cc2与cc引用同一个CtClass,但cc3不是。注意,在cc.setName(“Pair”)被执行之后,cc和cc1引用的CtClass对象表示的Pair类。

ClassPool对象通常在类和CtClass对象之间维护一个一对一的映射。Javassist从来不允许两个不同的CtClass对象表示同一个类,除非创建了两个独立的ClassPool。这是一致的程序转换的一个重要特征。

为了创建另一个由ClassPool.getDefault()返回的默认ClassPool实例的拷贝,执行下面这段代码(这段代码已经在上文展示过了):

ClassPool cp = new ClassPool(true);

如果你有两个ClassPool对象,你可以从每个ClassPool中分别获取一个表示同一个类文件的CtClass。你可以不同的改变这些CtClass对象去生成不同版本的类。

重命名一个冻结的类来创建一个新类

一旦CtClass对象被writeFile()或toBytecode() 转换到一个类文件,Javassist将拒绝这个CtClass对象的进一步修改。因此,在表示Point类的CtClass被转换到类文件之后,你不可以通过Point的拷贝来定义新的Pair类,因为Point将拒绝执行setName()。下面这段代码是错误的:

ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); cc.writeFile(); cc.setName("Pair"); // wrong since writeFile() has been called.

为了避免这个限制,你可以调用ClassPool的getAndRename()方法,例如,

ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); cc.writeFile(); CtClass cc2 = pool.getAndRename("Point", "Pair");

如果getAndRename()被调用,ClassPool第一次读Point.class来创建一个新的CtClass对象,然而,它在记录这个CtClass对象到hash表之前,将它从Pair重新命名成了Point。因此getAndRename()可以在表示Point类的CtClass调用writeFile()或者toBytecode()之后执行。

坚持原创技术分享,您的支持将鼓励我继续创作!
  • 本文作者:bdqfork
  • 本文链接:/articles/19
  • 版权声明:本博客所有文章除特别声明外,均采用BY-NC-SA 许可协议。转载请注明出处!
表情 |预览
快来做第一个评论的人吧~