Javassist 读写字节码

原文地址:奇舞移动技术

Javassist是一个用于处理Java字节码的库。Java字节码以二进制的形式存储在class文件中。 每个class文件包含一个Java类或接口。

class文件可以用Javassist.CtClass类来表示。CtClass对象用于处理class文件。以下是一个简单的例子,有两个类,这两个类没有关系,我们修改Rectangle使他的父类编程Point。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.democome.Rectangle");
cc.setSuperclass(pool.get("com.democome.Point"));
cc.writeFile();
复制代码

Rectangle.java

public class Rectangle {
	private int width;
	private int height;
}
复制代码

Point.java

public class Point {

}
复制代码

运行之后,用反编译工具可以看一下,继承关系已经改变:

1

上面的代码首先获得一个ClassPool。ClassPool是CtClass的容器。它读取class文件来构造CtClass对象,并记录。要修改calss,首先要从ClassPool通过get()获取一个CtClass对象。

关于实现的原理ClassPool中有一个Hashtable来存储CtClass,key就是类名。ClassPool的get()方法如果能找到这个类则直接返回,否则创建一个CtClass对象返回,并存到Hashtable中。

private Hashtable cflow = null;

CtClass对象对class文件进行修改,并调用writeFile()写入文件。Javassist还提供了一种直接获取修改后的字节码的方法:

byte[] b = cc.toBytecode();
复制代码

也可以直接加载class:

Class clazz = cc.toClass();
复制代码

定义一个类

定义一个类,需要调用ClassPool的makeClass()方法。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
复制代码

以上代码定义了一个类Point。可以用CtNewMethod创建一个方法,并使用CtClass中的addMethod()方法添加到Point。

cc.addMethod(CtNewMethod.make("public void hello(){System.out.print(\"hello\");}", cc));
cc.writeFile();
复制代码

2

如果要创建新接口,可以用ClassPool中的makeInterface()方法。可以用CtNewMethod中的abstractMethod()创建接口中的成员方法。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeInterface("Point");
cc.addMethod(CtNewMethod.abstractMethod(CtClass.voidType, "hello", null, null, cc));
cc.writeFile();
复制代码

3

冻结类

如果通过writeFile(),toClass()或toBytecode()将CtClass对象转换为类文件,Javassist将冻结该CtClass对象。 不允许对该CtClass对象进行进一步修改。 这是为了在开发人员尝试修改已加载的类文件时警告开发人员,因为JVM不允许重新加载类。

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

解冻之后可以修改,如下:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.democome.Rectangle");
cc.writeFile();
cc.defrost();
cc.setSuperclass(pool.get("com.democome.Point"));
复制代码

如果ClassPool.doPruning设置为true,那么当Javassist冻结该对象时,Javassist会修剪CtClass对象中包含的数据结构。修剪过的CtClass对象无法再次解冻。ClassPool.doPruning的默认值为false。要禁止修剪特定的CtClass,必须事先在该对象上调用stopPruning():

类搜索路径

ClassPool.getDefault()返回的ClassPool,类搜索路径和JVM相同。如果程序在诸如JBoss和Tomcat之类的Web应用程序服务器上运行,那么ClassPool可能无法找到用户类,因为这样的Web应用程序服务器使用多个类加载器以及系统类加载器。在这种情况下,必须在ClassPool中注册其他类路径。

pool.insertClassPath(new ClassClassPath(this.getClass()));
复制代码

以上代码注册用于加载此引用的对象的类的类路径。可以使用任何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);
复制代码

以上代码将“www.javassist.org:80/java/”添加到类搜…

http://www.javassist.org:80/java/org/javassist/test/Main.class
复制代码

还可以使用ByteArrayClassPath直接向ClassPool对象提供一个字节数组,并从该数组构造一个CtClass对象。例如:

ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("com.democome.Rectangle");
byte[] b = cc.toBytecode();
String name = "Rectangle";
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc2 = cp.get(name);
复制代码

如果不知道该类的完全限定名,则可以在ClassPool中使用makeClass():

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

例如:

ClassPool cp = ClassPool.getDefault();
InputStream ins = new FileInputStream(
		"/Users/yangpeng/Documents/workspace/javassist/Javassist/com/democome/Rectangle.class");
CtClass cc = cp.makeClass(ins);
System.out.println(cc.getName());

复制代码

打印结果如下:

com.democome.Rectangle
复制代码

关注微信公众号,最新技术干货实时推送

image

猜你喜欢

转载自juejin.im/post/5cbd21bc5188250a8a27aace