【Javassist官方文档翻译】第一章 读写字节码

本文详细介绍了Javassist库在Java字节码操作中的应用,包括读写字节码、创建新类、冻结与解冻类以及类的搜索路径设置。通过实例展示了如何使用Javassist修改类文件、定义新类以及管理类的搜索路径,为动态代理和字节码操作提供了实用指南。
摘要由CSDN通过智能技术生成

系列文章目录

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


前言

要想在JAVA程序运行时对原有的类进行增强或生成新类,就不得不说大名鼎鼎的动态代理技术。目前JAVA流行的动态代理框架主要有asm和Javassist两个。asm在性能上比Javassist要好。但Javassist操作字节码更简单,更容易入门。Javassist最好的教程就是官方文档。官网文档为英文的,对英语不好的Javaer来说不太友好。因此也有了翻译官网文档的计划。Javassist官网


读写字节码

Javassist 是一个用于操作 Java 字节码的类库。Java 字节码存储在类文件的二进制文件中。每个类文件都包含一个 Java 类或接口。
类Javassist.CtClass是对类文件的抽象表示。(编译时类CtClass)对象是处理类文件的句柄①。下面的程序是一个非常简单的例子:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();

该程序首先获取一个ClassPool对象,该对象控制使用 Javassist 修改字节码。 ClassPool对象是表示类文件CtClass的容器。它根据需要读取类文件用来构造CtClass对象,并记录构造的对象以响应之后对该对象的使用。要修改类的定义,用户必须首先从ClassPool对象中获取CtClass 对象的引用去代表这个类。ClassPool 的get()方法用于此目的。对于上述程序,第二行代码的意思是从ClassPool 对象pool 中获取代表test.Rectangle类的对象,并将其分配给一个变量cc 。 第一行代码的ClassPool 对象pool由getDefault()方法返回。该方法会按照系统的默认搜索路径取搜索。

从实现的角度来看,ClassPool是一个由CtClass对象组成的哈希表,它使用类名作为键。 ClassPool中的get方法会在哈希表中查找与指定键关联的CtClass对象。如果没有找到这样的对象,则 get方法会读取一个类文件去构造一个新 CtClass对象。最终该对象被记录在哈希表中,然后作为get方法的返回结果返回。

从ClassPool 获得的CtClass对象是可以被修改的(后面会详细介绍如何修改 )。在上面的示例中,我们将CtClass 对象cc的父类 test.Rectangle更改为类 test.Point。当CtClass对象cc的writeFile方法最终被调用时,此更改会反映在原始的类文件中。

writeFile方法将CtClass对象转换为类文件并将其写入本地磁盘。Javassist 也提供了直接获取修改后的字节码的方法。调用toBytecode方法即可获取字节码。

byte[] b = cc.toBytecode();

您也可以直接加载CtClass:

Class clazz = cc.toClass();

toClass方法会请求当前线程上下文的类加载器加载CtClass对象。 它返回一个java.lang.Class对象表示被加载的类。有关详细信息,请参阅下面的此部分

定义一个新类

要从头开始定义新类,ClassPool中的makeClass 必须被调用。.

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");

该程序定义了一个不包含任何成员的Point 类。Point类的成员方法可以被CtNewMethod中的工厂方法声明,然后用CtClass中的addMethod方法追加到Point类中。

makeClass方法无法创建一个新的接口,但是ClassPool 中的makeInterface方法可以创建。接口中的成员方法可以被CtNewMethod中的abstractMethod方法创建。这样去标记一个接口的方法为抽象方法。

冻结类

如果一个CtClass 对象由writeFile方法、toClass方法或toBytecode方法转换成一个类文件,Javassist 将会冻结那个CtClass 对象。从而不允许对那个CtClass 对象进行进一步的修改。这是为了在开发人员尝试修改已加载的类文件时警告开发人员,因为 JVM 不允许重新加载类。译者注:Java规范中规定:同一个ClassLoader对象中只能加载一次相同的class。

冻结的CtClass可以解冻,以便允许修改类定义。例如:

CtClasss cc = ...;
    :
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // OK since the class is not frozen.

在defrost方法被调用后,CtClass对象又可以被修改了。

如果ClassPool.doPruning设置为true,则当 Javassist 冻结该对象时,Javassist 会精简该对象中包含的数据结构。CtClass为了减少内存消耗,精简会丢弃该对象中不必要的属性(属性信息结构)。例如,代码属性结构(方法体)被丢弃。因此,在一个 CtClass对象被精简之后,除了方法名称、签名和注释之外的方法字节码是不可访问的。精简过的CtClass对象不能再次解冻。ClassPool.doPruning的默认值为false。

要禁止修剪特定的CtClass, stopPruning()必须提前在该对象上调用:

CtClasss cc = ...; 
cc.stopPruning(true); 
    : 
cc.writeFile(); // 转换为类文件。
// cc 没有被修剪。

CtClass对象cc没有被精简。因此它可以在writeFile方法被调用后解冻。

注意: 在调试时,您可能希望暂时停止精剪和冻结CtClass对象,并将修改的类文件写入磁盘驱动器上。 debugWriteFile方法可以很方便的实现那个目标。它会进行以下几个操作,首先停止精简,然后写入一个类文件,最后解冻它,然后再次打开修剪(如果精简设置最初是打开的话)。

类的搜索路径

默认的ClassPool 对象由静态方法ClassPool.getDefault() 返回,这个方法的搜索路径与底层JVM(Java virtual machine)的搜索路径相同。 如果程序在 JBoss 和 Tomcat 等 Web 应用程序服务器上运行,ClassPool对象可能无法找到用户的类 ,因为对于这样的 Web 应用程序,服务器会使用多个类加载器以及系统类加载器加载。在这种情况下,一个额外的类路径必须注册在ClassPool中。假设pool 引用一个ClassPool对象:

pool.insertClassPath(new ClassClassPath(this.getClass()));

这个语句注册了用于加载所this引用对象的类的类路径。您可以使用任何Class对象作为参数去替换this.getClass().。用于加载由该Class对象表示的类的类路径已注册。

您可以将目录名称注册为类的搜索路径。例如,以下代码添加一个目录 “/usr/local/javalib” 作为类的搜索路径:

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

用户可以添加的搜索路径不仅可以是一个目录,还可以是 URL:

ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

该程序将“http://www.javassist.org:80/java/”添加到类的搜索路径中。此 URL 仅用于搜索属于org.javassist包的类。例如,要加载一个类 org.javassist.test.Main,它的类文件将从以下位置获取:

http://www.javassist.org:80/java/org/javassist/test/Main.class

此外,您可以直接将字节数组赋予ClassPool 对象,然后根据那个数组构造一个CtClass 对象。为了做到这一点,请使用ByteArrayClassPath。 例如:

ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);

获得的CtClass对象表示由b的类文件定义的类。如果CtClass 的get方法被调用,并且参数name与ByteArrayClassPath中的name相同,那么ClassPool 将会从ByteArrayClassPath给的路径中去读取类文件。

如果您不知道类的完全限定名称,那么您可以使用ClassPool中的makeClass方法:

ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

makeClass方法返回从给定输入流构造的CtClass 对象。如果你比较着急的话,您可以用makeClass方法将类文件提供给ClassPool对象。如果搜索路径包含较大的 jar 文件,这可能会提高性能。由于ClassPool对象是按需读取类文件,因此它可能会重复搜索整个 jar 文件以查找每个类文件。 makeClass方法可用于优化这种搜索。由makeClass方法构造的CtClass类文件 会被留存在ClassPool 对象中,它的类文件永远不会被重复读取。

用户可以扩展类的搜索路径。他们可以定义一个实现ClassPath接口的新类并将该类的实例提供给ClassPool中的insertClassPath方法。这允许在搜索路径中包含非标准资源。

总结

本篇文章介绍了Javassist的背景和用途,并且讲解了Javassist对于字节码读取的操作。主要从定义一个新类、冻结类和类的搜索路径来介绍字节码读取的操作。

说明

①.句柄(Handle)是一个是用来标识对象或者项目的标识符

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值