【Javassist官方文档翻译】第二章 类池

系列文章目录

第一章 读写字节码
第二章 类池



前言

在上一章我们介绍了Javassist读取字节码的一些操作,本章我们会介绍Javassist中的ClassPool。


类池

ClassPool对象是由许多个CtClass对象构成的容器。一旦一个CtClass对象被创建出来,它就会被永远地记录在某个ClassPool中。这是因为编译器在编译引用该CtClass所表示的类的源代码时可能需要访问CtClass对象。如果CtClass对象所代表的Point 类丢失的话,编辑器将不能编译对getter方法的调用。

例如,一个新方法getter被添加到一个CtClass 对象所代表的Point类中。稍后,该程序尝试编译包括对Point中getter方法调用的源代码,,并将编译后的代码用作方法体,该方法体将被添加到另一个类Line中。

避免内存溢出

如果CtClass对象的数量变得非常多(这种情况很少发生,因为Javassist试图以各种方式减少内存消耗),那么类池的规模可能会导致巨大的内存消耗。为了避免这个问题,可以从类池中显式删除不必要的CtClass对象。如果你调用CtClass对象的detach方法,那么这个CtClass对象将从类池中删除。例如:

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

在一个CtClass 对象的detach方法被调用后,您将不能再调用该对象的任何方法。然而你可以调用ClassPool 对象的get方法来创建一个原来的CtClass 对象的新实例。如果你调用了ClassPool 对象的get方法,ClassPool 会再次读取一个类文件,然后去创建一个由get方法返回的新的CtClass 对象。

另一个解决方案是用一个新N收,则该类池中包含的CtClass对象也会被垃圾收集器回收。要创建新的ClassPool的实例,请执行以下代码段:

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

以上代码创建了与ClassPool.getDefault方法返回的默认ClassPool 具有相同的行为。注意ClassPool.getDefault方法是一个提供便捷的单例工厂方法。它以如上所示的方式创建一个ClassPool 对象。尽管它保留了一个ClassPool 实例并重用它,getDefault方法返回的ClassPool 对象并没有特殊用途。getDefault方法是一个简便的方法。

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

ClassPool cp = new ClassPool(); 
cp.appendSystemPath(); // 或者通过 appendClassPath() 附加另一个路径

级联类池

如果程序在 Web 应用程序服务器上运行,则可能需要创建多个Classpool实例;ClassPool应该为每个类加载器(即容器)创建一个实例。程序不应该通过调用getDefault方法创建ClassPool 对象,而应该使用ClassPool的构造器去创建ClassPool 对象。

多个ClassPool 对象可以像java.lang.ClassLoader一样串联在一起。例如:

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

如果child的get方法被调用,子类池首先委托给父类池去获取CtClass对象。如果父类池没有发现CtClass对象的类文件,则子类池尝试在./classes目录下去查找CtClass对象的类文件。

如果child 的childFirstLookup 属性为true的话,子类池会尝试在委托父类池之前查找CtClass对象的类文件。例如:

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 对象cc赋予一个新的名字Pair 。在这次的方法调用之后,由该对象表示的类定义中所有出现的类名都从Point 更改为Pair。类定义的其他部分没有改变。

注意 ,CtClass 对象中的setName方法会改变ClassPool 对象中的一条记录。从实现的角度来看,ClassPool 对象是一个由CtClass 对象组成的哈希表。setName方法改变哈希表中CtClass 对象关联的key。key从原始的类名改成了新的类名。

因此,如果get(“Point”)方法稍后在 ClassPool对象上再次调用,则它永远不会返回 CtClass变量cc引用的对象。该ClassPool对象再次读取一个类文件 Point.class,并CtClass 为 class 构造一个新对象Point。这是因为与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引用着相同的CtClass实例,cc3却与cc引用的实例不同。注意在cc.setName(“Pair”)这行语句执行之后,cc和cc1的CtClass 对象指的是Pair类。

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

如果想要创建由ClassPool.getDefault方法返回的默认实例的另一个副本,请执行以下代码片段(此代码已在上面显示):

ClassPool cp = new ClassPool(true);

如果你有两个ClassPool 对象,你就可以从每一个ClassPool中获取表示相同的类文件的不同的CtClass 对象。你可以对这些CtClass 对象进行不同的修改然后生成这个类的不同版本。

重命名冻结类以定义新类

一旦一个CtClass 对象由writeFile方法或toBytecode方法转换成一个类文件,Javassist 会拒绝对那个CtClass 对象更多的修改。因此,在CtClass 对象所代表的Point 类被转换成类文件之后。你将无法定义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类去创建一个表示Point 类的新的CtClass 对象。但是,getAndRename方法会在将该对象记录在哈希表中之前将该对象从Point重命名为Pair。因此getAndRename方法可以在writeFile方法和toBytecode方法在CtClass 对象所表示的Point 类上调用之后执行。

总结

本篇文章介绍了Javassist的类池ClassPool,主要讲解了级联类池的用法以及CtClass对象的数量变得非常多的时候如何避免内存溢出的功能。

说明

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值