javassist翻译之二《ClassPool》

一个ClassPool对象是一个包含很多CtClass对象的容器。一个CtClass对象只要被创建一次,它就会一直在ClassPool中。这是因为编译器在编译该CtClass表示的类的源代码时可能需要稍后访问CtClass对象。

例如,假设一个新的方法getter( )被添加到代表Point类的CtClass对象中。稍后,程序将尝试编译源代码,包括在Point中调用getter( )方法,并使用编译后的代码作为方法的主体,将其添加到另一个类Line中。如果表示Point的CtClass对象丢失,则编译器无法将方法调用编译为getter( )。请注意,原始类定义不包括getter( )。 因此,要正确编译这样的方法调用,ClassPool必须包含所有程序执行时的CtClass的所有实例。


避免内存泄漏

如果CtClass对象的数量变得非常大(这很少发生,因为Javassist试图以各种方式减少内存消耗),这种ClassPool规范可能会导致巨大的内存消耗。为了避免这个问题,你可以明确的从ClassPool中删除一些不必要的CtClass对象。如果在CtClass对象上调用detach( )方法,那么该CtClass对象将从ClassPool中移除。例如:

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

调用detach( )方法之后,你不能在该CtClass对象上调用任何方法。但是,您可以调用ClassPool上的get( )来创建一个代表相同类的CtClass的新实例。 如果调用get( ),则ClassPool将再次读取同一个类文件,并新创建一个由get( )返回的新的CtClass对象。

另一个想法是偶尔用新的ClassPool替换掉旧的。 如果一个旧的ClassPool被垃圾收集,那么包含在该ClassPool中的CtClass对象也被当做垃圾收集。要创建ClassPool的新实例,请执行以下代码片段:

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

这创建一个ClassPool对象,其行为与ClassPool.getDefault( )所返回的默认ClassPool一样。请注意,ClassPool.getDefault( )是为了方便而提供的一个单例工厂方法。它以上面所示的相同方式创建一个ClassPool对象,虽然它保留了一个ClassPool的实例并重用它。 getDefault( )返回的ClassPool对象没有特殊的作用。 getDefault( )是一个方便的方法。

请注意,new ClassPool(true)是一个方便的构造函数,它构造一个ClassPool对象并将系统搜索路径附加到它。 调用该构造函数等价于下面的代码:

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

如果程序在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,那么子类ClassPool在委托给父类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。setName( )方法调用之后,由该CtClass对象表示的类定义中的所有的Point都被替换为Pair。类中的其他内容不会发生任何变化。

请注意,CtClass中的setName( )更改了ClassPool对象中记录的CtClass对象。从实现的角度来看,一个ClassPool对象是一个包含很多CtClass对象的哈希表。 setName( )更改哈希表中特定的CtClass对象与其关联的键。这个键从原来的类名改为新的类名。

因此,如果稍后在ClassPool对象上调用get(“Point”),则它永远不会返回一个CtClass对象并赋值给变量cc。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引用的都是Pair类的CtClass对象。

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

如果想要创建一个由ClassPool.getDefault( )返回的ClassPool的默认实例的副本,请执行以前代码片段:

ClassPool cp = new ClassPool(true);

如果你有两个ClassPool对象,则可以分别从每个ClassPool实例中获取到表示相同类文件的不同CtClass对象。你可以修改这些CtClass对象来生成不同版本的类。


重命名一个冻结的类来定义一个新的类

一旦CtClass对象调用writeFile( )或toBytecode( )方法将定义的类转换成类文件,Javassist将拒绝对该CtClass对象的进一步修改。

在将表示Point类的CtClass对象转换为类文件后(通过调用writeFile()或toBytecode( )方法,CtClass对象将被冻结),由于不能在Point类的CtClass对象上执行setName( )方法,所以无法将Pair类定义为Point类的副本。如下是错误的代码示例:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile();        //调用此方法后,cc对象将被冻结,无法修改。
cc.setName("Pair");    // wrong since writeFile() has been called.

为了避免上述的这种限制,你可以调用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对象从“Point”重命名为“Pair”前,它被存储在一个hash table中。因此getAndRename( )方法可以在代表Point类的CtClass对象调用writeFile( )或toBytecode( )之后执。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值