前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >javassist编程指南==ClassPool 类池

javassist编程指南==ClassPool 类池

作者头像
青山师
发布2023-05-05 19:16:55
4250
发布2023-05-05 19:16:55
举报
文章被收录于专栏:IT当时语_青山师_JAVA技术栈

ClassPool 类池

一个ClassPool对象是包含CtClass对象的容器。一旦一个CtClass对象被创建后,就会被记录到一个ClassPool中。这是因为编译器在编译源码时会引用代表CtClass的类,可能会访问CtClass对象。

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

避免内存不足

如果对象存在惊人大量的CtClassClassPool的这种规范则可能会引起极大的内存消耗(这其实很少会发生,因为javassist会以各种方式降低内存开销:冻结calss等方式)。为了避免这种问题,你需要从ClassPool中明确移除不必要的CtClass对象。

可以调用CtClassdetach()方法,然后会将该对象从ClassPool中移除:

代码语言:javascript
复制
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.get("org.byron4j.cookbook.javaagent.Javassist2ClassPool");

// 调用该方法后,会将CtClass对象从ClassPool中移除
cc.detach();

调用CtClass.detach()方法后,你不应该再继续调用这个ctClass对象的其他方法了。可是,你可以调用ClassPool.get()方法会重新读取class文件构造一个表示相同的类的ctClass对象。

另一方案是,偶尔使用一个新的classPool代替旧的classPool。如果旧的classPool被gc了,包含在其中的ctClass也会被gc掉。

代码语言:javascript
复制
ClassPool cp = new ClassPool(true)

级联ClassPool

如果程序运行在一个web应用中,是有必要创建ClassPool的多实例的。每一个类加载器应该只创建一个ClassPool对象。此时程序不应该使用getDefault()方法而是使用ClassPool的构造方法。

多个ClassPool对象可以像java.lang.ClassLoader一样级联:

代码语言:javascript
复制
// 级联ClassPool
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");

如果调用了child.get()方法,child首先委托父ClassPool,如果父ClassPool加载class文件失败,然后child再尝试从./classes/目录下查找类文件。

如果child.childFirstLookup设置为true,则child尝试在委托给父classPool之前去加载class文件:

代码语言:javascript
复制
// child classpool在委托之前加载类文件
ClassPool parent2 = ClassPool.getDefault();
ClassPool child2 = new ClassPool(parent2);
child2.appendSystemPath();         // 和默认同样的class查找路径
child2.childFirstLookup = true;    // 改变child的行为

更改类名以定义新类

一个新的class可以被定义为一个已存在的类的副本。

代码语言:javascript
复制
// 首先获取表示Point类的一个CtClass对象
// 然后调用setName修改类名
ClassPool pool3 = ClassPool.getDefault();
CtClass cc3 = pool3.get("org.byron4j.cookbook.javaagent.Point");
cc3.setName("Pair");

这个程序首先包含类Point的ctClass对象,然后调用setName()方法为CtClass对象设置新的名称。在这个调用之后,所有由该CtClass对象定义的所有类名展示均由Point变更为Pair,类定义的其他部分则不会变更。

注意: CtClass.setName()方法改变了ClassPool中记录的对象。从实现的角度来看,一个ClassPool对象是CtClass对象的哈希表形式。setName会改变CtClass对象在哈希表中关联的key,key值从原来的类名变更为新的设置后的类名。 因此,如果后面再调用get("Point")方法的话,不会再返回变量cc3引用的CtClass对象了,ClassPool对象会再次读取class文件Point.class并且为Point类构造一个新的CtClass对象,这是因为ClassPool对象中关联Point的key不存在了:

代码语言:javascript
复制
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("org.byron4j.cookbook.javaagent.Point");
CtClass ctClass1 = pool.get("org.byron4j.cookbook.javaagent.Point");
ctClass.setName("Pair");
CtClass ctClass2 = pool.get("Pair");
CtClass ctClass3 = pool.get("org.byron4j.cookbook.javaagent.Point");
System.out.println(ctClass == ctClass2);  // true;
System.out.println(ctClass3 == ctClass2); // false;

ClassPool对象用于维护类和CtClass的一对一映射关系。javassist不允许两个不一样的CtClass表示同一个class,除非是两个独立的ClassPool创建的。

为了创建一个默认ClassPool实例(Clas.getDefault()返回的)的一个副本,可以使用以下代码片段:

代码语言:javascript
复制
ClassPool cp = new ClassPool(true);

这样一来,你拥有了两个ClassPool对象,可以从每一个ClassPool提供不同的CtClass对象表示同一个类。

代码语言:javascript
复制
ClassPool pool10 = ClassPool.getDefault();
CtClass ctClass10 = pool10.get("org.byron4j.cookbook.javaagent.Point");
ClassPool pool20 = new ClassPool(true);
CtClass ctClass20 = pool20.get("org.byron4j.cookbook.javaagent.Point");
System.out.println(pool10 == pool20);   // false 不同的ClassPool中表示同一个类的CtClass对象

通过重命名一个冻结的CtClass来创建一个新的CtClass对象

一旦一个CtClass对象已经被writeFile()或者toBytecode()方法转到class文件,Javassist拒绝进一步修改该CtClass对象。因此,如果代表Point类的CtClass对象冻结后不能通过setName()修改它的名称。

代码语言:javascript
复制
ClassPool pool30 = ClassPool.getDefault();
CtClass ctClass30 = pool30.get("org.byron4j.cookbook.javaagent.Point");
ctClass30.writeFile();// 被冻结
ctClass30.setName("Pair");// 错误

为了打破这个约束,可以使用ClassPool的getAndRename()方法:

代码语言:javascript
复制
ClassPool pool30 = ClassPool.getDefault();
CtClass ctClass30 = pool30.get("org.byron4j.cookbook.javaagent.Point");
ctClass30.writeFile();// 被冻结
//ctClass30.setName("Pair");// 冻结后不能使用--错误
pool30.getAndRename("org.byron4j.cookbook.javaagent.Point", "Pair");

因为如果getAndRename方法被调用了,ClassPool首先读取Point.class文件来创建一个新的CtClass对象,会在记录到ClassPool的哈希表之前将Point变更为Pair。 getAndRename方法的源码:

代码语言:javascript
复制
public CtClass getAndRename(String orgName, String newName)
        throws NotFoundException
{
    // 获取一个新的CtClass对象
    CtClass clazz = get0(orgName, false);
    if (clazz == null)
        throw new NotFoundException(orgName);

    if (clazz instanceof CtClassType)
        ((CtClassType)clazz).setClassPool(this);

    // 设置新的名称
    clazz.setName(newName);         // indirectly calls
                                    // classNameChanged() in this class
    return clazz;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ClassPool 类池
    • 避免内存不足
      • 级联ClassPool
        • 更改类名以定义新类
          • 通过重命名一个冻结的CtClass来创建一个新的CtClass对象
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档