Javassist读写字节码

简介: Javassist读写字节码

@[TOC]

前言

要想在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,它的类文件将从以下位置获取:

$$ \text 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)是一个是用来标识对象或者项目的标识符

相关文章
|
5月前
|
存储 Java
JAVA程序运行问题之编译生成的字节码在不同的平台上是否相同如何解决
JAVA程序运行问题之编译生成的字节码在不同的平台上是否相同如何解决
|
6月前
|
Oracle Java 关系型数据库
Java字节码指令大全
Java字节码指令大全
48 0
|
存储 Java 开发者
JVM篇【Java源文件和Class字节码文件对比】
JVM篇【Java源文件和Class字节码文件对比】
191 0
JVM篇【Java源文件和Class字节码文件对比】
|
存储 安全 Java
【JVM原理探索】class字节码指令操作介绍(上)
【JVM原理探索】class字节码指令操作介绍(上)
102 0
【JVM原理探索】class字节码指令操作介绍(上)
Java中把jar包内文件资源释放出来的方法
Java中把jar包内文件资源释放出来的方法
218 0
|
人工智能 自然语言处理 安全
JVM字节码(class文件)解析
JVM字节码(class文件)解析
142 1
|
Java 索引
【JVM原理探索】class字节码指令操作介绍(下)
【JVM原理探索】class字节码指令操作介绍(下)
99 0
|
Java 编译器
字节码文件
字节码文件
161 0
Java工具篇之Javassist字节码编程
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。 相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。javassist简单易用, 快速。
426 0
|
Java
从Java进程里dump出类的字节码文件
想要查看一些被增强过的类的字节码,或者一些AOP框架的生成类,就需要dump出运行时的Java进程里的字节码。 从运行的java进程里dump出运行中的类的class文件的方法: 用agent attatch 到进程,然后利用Instrumentation和ClassFileTransformer就可以获取到类的字节码了。
2586 0