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 {
}
运行之后,用反编译工具可以看一下,继承关系已经改变:
上面的代码首先获得一个 ClassPool。ClassPool 是 CtClass 的容器。它读取 class 文件来构造 CtClass 对象,并记录。要修改 calss,首先要从 ClassPool 通过 get( ) 获取一个 CtClass 对象。
关于实现的原理 ClassPool 中有一个 Hashtable 来存储 CtClass,key 就是类名。ClassPool 的 get( ) 方法如果能找到这个类则直接返回,否则创建一个 CtClass 对象返回,并存到 Hashtable 中。
privateHashtablecflow=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();
如果要创建新接口,可以用 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();
冻结类
如果通过 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);
以上代码将 “http://www.javassist.org:80/java/” 添加到类搜索路径中。此 URL 仅用于搜索属于包 org.javassist 的类。例如,要加载类 org.javassist.test.Main ,其类文件将从以下位置获取:
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
结语
为了更好的交流互动,我们奇舞移动创建了一个技术交流微信群,我们可以聊一些流行技术,也可以聊聊工作中遇到的问题,对我们公众号有什么意见和建议也欢迎提出,扫码进群
也可以加小编微信,备注奇舞移动噢
--END--
识别二维码,关注我们