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

相关文章
|
3月前
|
Java 编译器 Maven
一文解读|Java编译期注解处理器AbstractProcessor
本文围绕编译器注解都是如何运行的呢? 又是怎么自动生成代码的呢?做出了详细介绍。
379 0
|
3月前
|
存储 算法 Java
Java字节码编程之非常好用的javassist
Java字节码编程之非常好用的javassist
100 0
JavaIO流 文件的读写
JavaIO流 文件的读写
|
存储 Java 开发者
JVM篇【Java源文件和Class字节码文件对比】
JVM篇【Java源文件和Class字节码文件对比】
174 0
JVM篇【Java源文件和Class字节码文件对比】
Java中把jar包内文件资源释放出来的方法
Java中把jar包内文件资源释放出来的方法
200 0
|
XML 存储 Java
Java读写资源文件类Properties
Java读写资源文件类Properties
110 0
Java读写资源文件类Properties
|
Java
Java 读写一个文件
Java 读写一个文件
137 0
Java 读写一个文件
Java工具篇之Javassist字节码编程
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。 相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。javassist简单易用, 快速。
382 0
Java:文件写入读取操作和工具类
Java:文件写入读取操作和工具类
344 0
|
Java API
字节码编程,Javassist篇五《使用Bytecode指令码生成含有自定义注解的类和方法》
到本章为止已经写了四篇关于字节码编程的内容,涉及了大部分的API方法。整体来说对 Javassist 已经有一个基本的使用认知。那么在 Javassist 中不仅提供了高级 API 用于创建和修改类、方法,还提供了低级 API 控制字节码指令的方式进行操作类、方法。
236 0
字节码编程,Javassist篇五《使用Bytecode指令码生成含有自定义注解的类和方法》