Java字节码之Javassist开启之旅(一)

文章目录

  • 读写JAVA字节码
  • 定义新的类
  • 冻结类
  • 类搜索路径

读写JAVA字节码

  Javassit是用来处理Java字节码的类库。Java字节码保存在Class二进制文件中,每个Class文件包含一个Java类或者接口。

  Javassist.CtClass是一个Class二进制文件的抽象表示, CtClass(编译时类)对象是用来处理Class文件的句柄。代码示例如下:

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()方法用于此目的。在上面所示的程序中,通过getDefault()返回的搜索默认为系统搜索路径的ClassPool对象pool。通过get方法从ClassPool对象pool中获得的test.Rectangle类对象cc。

  从实现的角度来看,ClassPool是一个CtClass对象的哈希表,它使用类名作为key。在ClassPool中搜索这个哈希表以找到与指定键相关联的CtClass对象。如果没有找到这样的CtClass对象,get()将读取一个类文件来构造一个新的CtClass对象并记录在哈希表中,然后作为get()的结果值返回。

  从ClassPool对象获得的CtClass对象可以被修改(稍后的章节将会介绍)。在上面的示例中,test.Rectangle的父类被改成了test.Point。当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不允许重新加载类。

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

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

调用defrost()后,可以再次修改CtClass对象。

  如果ClassPool的doPruning被设置为true,那么当Javassist冻结CtClass对象时,Javassist将删除该对象中包含的数据结构,为了减少内存消耗,修剪会丢弃该对象中不必要的属性(attribute_info结构)。因此,在修剪CtClass对象之后,除了方法名称、签名和注释之外,方法的字节码是不可访问的。修剪后的CtClass对象不能再次解冻。ClassPool的doPruning默认值为false。

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

CtClasss cc = ...; 
cc.stopPruning(true);
cc.writeFile(); // convert to a class file. // cc is not pruned.

上述示例中CtClass对象cc没有被修剪。因此可以在调用writeFile()后解冻。

类搜索路径

  静态方法ClassPool. getdefault()返回的默认ClassPool搜索与底层JVM (Java虚拟机)拥有的相同路径。如果程序运行在JBoss和Tomcat等web应用服务器上,ClassPool对象可能无法找到用户类,因为这样的web应用服务器使用多个类装入器以及系统类装入器。在这种情况下,必须向ClassPool注册一个额外的类路径。假设池引用一个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](http://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/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);

如果你不知道类的全限定名,那么你可以在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()。这允许在搜索路径中包含非标准资源。


[资料英文来源]  http://www.javassist.org/tutorial/tutorial.html

你可能感兴趣的:(Java,java,开发语言,jvm)