分享

Javassist 教程(一)

 飞鹰飞龙飞天 2014-03-11
1、读写字节码
     Javassist是一个处理Java字节码的类库。Java字节码存储在名叫class file的二进制文件里。每个class文件包含一个Java类或者接口。
     Javassit.CtClass是一个class文件的抽象表示。一个CtClass(compile-time class)对象可以用来处理一个class文件。下面的代码是一个简单的示例:
     ClassPool pool = ClassPool.getDefault();
     CtClass cc = pool.get("test.Rectangle");
     cc.setSuperclass(pool.get("test.Point"));
     cc.writeFile();
     这段代码首先是去得一个ClassPool对象,它负责用Javassist来控制字节码的修改。ClassPool对象是CtClass对象的一个容器。它按照需要读取class文件来创建CtClass对象,然后记录这个对象用于后续的操作。要修改一个类的定义,首先需要从一个ClassPool里取得表示这个类的CtClass对象的引用。ClassPool的get()方法就是起这个作用的。在上面的例子里,表示test.Rectangle的CtClass是从ClassPool里取到的,并且赋值给变量cc。通过getDefault()返回的ClassPool会搜索系统的默认搜索路径。

     从实现的角度来讲,ClassPool是一个包含CtClass对象的hash table,它用class名词作为key。调用ClassPool的get()方法,其实就是通过名称来搜索指定的类。如果在hash table中没有找到,get()方法会读取对应的class文件,创建一个新的CtClass对象,把它保存在hash table李,然后作为结果返回。

     从ClassPool里取得的CtClass对象是可以修改的。在上面的例子里,test.Rectangel的父类被修改为test.Point。这个修改会在最后调用writeFile()方法的时候体现到原始的class文件上。
     writeFile()方法把一个CtClass对象转换成一个class文件,然后把它写入到本地磁盘。Javassist也提供了直接获取修改后的字节码的方法。要获取字节码,调用toByteCode()方法:
     byte[] b = cc.toByteCode()
     当然,你也可以直接加载修改后的CtClass
     toClass()方法请求当前线程上下文的class loader去加载这个CtClass所表示的类。它返回一个表示这个类的java.lang.Class实例。

2、定义一个新的类
     要完全定义一个新的类,需要在ClassPool上调用makeClass()方法。
          ClassPool cp = ClassPool.getDefault();
          CtClass cc = cp.makeClass("Point");
     这段代码定义了一个没有成员变量的Point类。Point的成员方法可以通过调用CtNewMethod的工厂方法来创建,然后用CtClass的addMethod()方法添加到Point类上去。

     makeClass()不能用来创建接口;ClassPool的makeInterface()方法可以做到。接口里的成员方法可以用CtNewMethod的abstractMethod()来创建。注意接口的方法都是抽象方法。


冻结class
     如果一个CtClass对象通过writeFile(),toClass()或者toBytecode()转换成了class文件,那么Javassist会冻结这个CtClass对象。后面就不能继续修改这个CtClass对象了。这样是为了警告开发者不要修改已经被JVM加载的class文件,因为JVM不允许重新加载一个类。
     一个被冻结的CtClass可以通过解冻来使得后续可以继续修改这个类的定义。例如,
     CtClass cc = ...;
          .
          .
     cc.writeFile();
     cc.defrost();
     cc.setSuperclass(...);// 可以修改,因为这个class没有被冻结


     在调用defrost()方法后,CtClass对象有可以被编辑了。

     如果ClassPool.doPruning被设置成true,那么Javassist会在冻结一个对象的时候对这个对象进行精简。为了减少ClassPool的内存占用,精简的时候会丢弃class中不需要的属性。例如Code_attribute结构(即是方法体)会被丢弃。因此,如果一个CtClass对象被精简了,那么方法的字节码是不能访问的,留下的只有方法名,方法的签名和annotation。被精简的CtClass对象不能够再被defrost。ClassPool.doPruning的默认值是false。

     如果要阻止对某一个具体的CtClass对象的精简,需要在这个对象上先调用stopPruing()方法:
          CtClass cc = ...;
          cc.stopPruning(true);
               .
               .
          cc.writeFile();     //   转换成一个class文件
          //cc没有被精简     

     这个CtClass对象cc没有被精简。所以在调用writeFile()方法后可以被解冻。


类搜索路径
     静态方法ClassPool.getDefault()返回的默认ClassPool的类搜索路径和当前的JVM保持一致。如果一个程序运行在JBoss或者Tomcat这样的web service上时,ClassPool可能会找不到用户的class,因为像这样的web service除了用system class loader外,还会用多个其他的class loader。在这种场景下,需要把额外的搜索路径注册到ClassPool上去。假设下面的pool表示一个ClassPool:
     pool.insertClassPath(new ClassClassPath((this.getClass()));
这个语句把装载this所引用的class的class path注册到ClassPool上。你可以用任何的Class对象做为参数来替代this.getClass(),用来装载Class的class path被会被注册进来。

     你也可以用一个目录名作为类搜索路径。例如,下面的代码吧/usr/local/javalib加到当前的搜索路径里:
     ClassPool pool = ClassPool.getDefault();
     pool.insertClassPath("/usr/local/javalib");
     你可以添加的不仅仅是一个目录也可以是一个URL:
          ClassPool pool = ClassPool.getDefault();
          ClassPath cp = new URLClassPath("www.",80,"/java/","org.javassist.");
          pool.insertClassPath(cp);
     上面的代码吧“http://www.:80/java/”到类搜索路径上。这个URL仅仅只是被用来搜索org.javassist.包里的类。例如,要加载org.javassist.test.Main,这个类会被从:
     更进一步说,你可以直接给ClassPool类传入一个byte数组,然后从这个数组里创建一个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所指定的class文件所代表的类。如果调用get()方法的时候,如果传入的名称和之前指定的name一致的话,ClassPool会从指定的ByteArrayClassPath读入一个class文件。

     如果你不知道这个类的全局限定名,那么你就可以用ClassPool的makeClass()方法:

  • ClassPool cp = ClassPool.getDefault(); InputStream ins = an input stream for reading a class file; CtClass cc = cp.makeClass(ins); makeClass()会返回从指定的输入流创建的CtClass对象。你可以makeClass()来更快地把一个class文件加入到ClassPool里。这样做会在搜索路径里包含一个很大的jar文件的时候提高性能。因为ClassPool是即时读取class文件的,它可能会在每次加载类的时候都需要搜索整个jar文件。makeClass()可以用来优化这个搜索。这个由makeClass()创建的CtClass会保留在ClassPool里,并且class文件不需要再次读入了。

          用户也可以扩展类搜索路径。他们可以实现ClassPath接口,这样就可以让一些不符合规范的资源也能够加入到类搜索路径里。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多