ClassPool对象是CtClass对象的容器,一旦创建了CtClass对象,它就会被永久地记录在ClassPool中。这是因为编译器稍后在编译引用由该CtClass表示的类的源代码时可能需要访问CtClass对象。
例如,假设一个新的方法getter()被添加到表示Point类的CtClass对象中。稍后,程序尝试编译源代码包括Point中的getter()方法调用,并使用编译后的代码作为方法体,该方法体将被添加到另一个类Line。如果CtClass对象表示点丢失,编译器不能编译方法调用的getter()。请注意,最初的类定义不包括getter()。因此,为了正确地编译这样的方法调用,ClassPool必须包含程序执行期间所有的CtClass实例。
避免内存溢出
如果CtClass对象的数量变得惊人的大,ClassPool的这种规范可能会导致巨大的内存消耗。为了避免这个问题,您可以显式地从ClassPool中删除一个不必要的CtClass对象。如果你在一个CtClass对象上调用detach(),那么该CtClass对象将从ClassPool中移除。例如:
CtClass cc = ... ;
cc.writeFile();
cc.detach();
在detach()被调用之后,你不能调用CtClass对象上的任何方法。但是,您可以在ClassPool上调用get()来创建一个表示相同类的CtClass的新实例。如果调用get(), ClassPool将再次读取类文件并新建一个CtClass对象并返回。
另一个想法是用一个新的ClassPool替换一个旧的ClassPool。如果一个旧的ClassPool被垃圾回收,该ClassPool中包含的CtClass对象也被垃圾收集。要创建一个新的ClassPool实例,请执行以下代码片段:
ClassPool cp = new ClassPool(true);
这将创建一个ClassPool对象,其行为与ClassPool. getdefault()返回的默认ClassPool相同。ClassPool.getDefault()是为了方便而提供的单例工厂方法。
注意,new ClassPool(true)是一个方便的构造函数,它构造一个ClassPool对象并将系统搜索路径附加到它。调用该构造函数等价于以下代码:
ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()
级联的ClassPool
如果一个程序运行在web应用服务器上,创建多个ClassPool实例可能是必要的;应该为每个类装入器(即容器)创建一个ClassPool实例。程序应该不调用getDefault()而是调用ClassPool的构造函数来创建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,则子ClsssPool在委托给父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对象的哈希表。setName()更改哈希表中与CtClass对象相关的键。键从原来的类名更改为新的类名。
因此,如果稍后在ClassPool对象上再次调用get(“Point”),则它永远不会返回变量cc引用的CtClass对象。ClassPool对象再次读取类文件Point.class,并为类Point构造一个新的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对象被转换为类文件后,您不能将Pair类定义为Point的副本,因为在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以创建一个表示Point类的新CtClass对象。但是,在将该CtClass对象记录到哈希表之前,它将该CtClass对象从“点到对”重命名。因此,在表示Point类的CtClass对象上调用writeFile()或toBytecode()后,可以执行getAndRename()。
[资料英文来源] http://www.javassist.org/tutorial/tutorial.html