logo资料库

javassist官方手册(中文).docx

第1页 / 共50页
第2页 / 共50页
第3页 / 共50页
第4页 / 共50页
第5页 / 共50页
第6页 / 共50页
第7页 / 共50页
第8页 / 共50页
资料共50页,剩余部分请下载后查看
1. 读写字节码
定义一个新类
冻结类
类搜索路径
2. ClassPool
避免内存溢出
层叠 ClassPool
通过改变类名来定义新类
通过重命名冻结类来定义新的类
3. 类加载器
3.1 CtClass.toClass( ) 方法
3.2 Java中的类加载器
3.3 使用 javassist.Loader
3.4编写类加载器
3.5 修改系统类
3.6 运行时重载类
4. 反射和自定义
4.1 在方法的开头/结束插入源代码
$0, $1, $2, ...
$args
$$
$cflow
$r
$w
$_
$sig
$type
$class
addCatch()
4.2 修改方法体
使用表达式替换源码
javassist.expr.MethodCall
javassist.expr.ConstructorCall
javassist.expr.FieldAccess
javassist.expr.NewExpr
javassist.expr.NewArray
javassist.expr.Instanceof
javassist.expr.Cast
javassist.expr.Handler
4.3 添加新方法或成员
添加方法
相互调用方法
添加成员
删除成员
4.4 注解
4.5 运行时支持类
4.6 导入
4.7 限制
5. 字节码操作API
5.1 获取ClassFile对象
5.2 添加和删除成员
5.3 遍历方法体
5.4 生成字节码序列
5.5 注解 (Meta 标签)
6. 泛型
Javassist 入门手册 1. 读写字节码 Javassist 是一个 Java 字节码操作类库, Java 字节码被保存在一个被称为 class 文件的二进制文件中, 每个类文件都包含一个 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 的 get()方法来得到代表这个类的 CtClass 对象。 如上所述,我们从 ClassPool 对象中获取代表类 test.Rectangle 的 CtClass 对 象,并赋值给变量 cc。getDefault()方法用于搜索默认的系统路径并返回 ClassPool 对象。 从实现的角度看,ClassPool 就是 CtClass 对象的哈希表,以类名称作为键值。 ClassPool 的 get()方法通过指定的键值来搜寻 CtClass 对象。 通过 ClassPool 获取到的 CtClass 对象可被修改(后面将展示如何修改 CtClass)。 在上面的例子中,类 test.Rectangle 的父类被修改为 test.Point。这个变化将 会通过 CtClass 的 writeFile()方法调用最终实现。
writeFile()方法将 CtClass 对象转化为类文件并写入磁盘中。另外,Javassist 还提供了一个直接获取和修改字节码的方法 toBytecode(): byte[] b = cc.toBytecode(); 你也可以直接加载 CtClass: Class clazz = cc.toClass(); toClass()方法会要求类加载器的当前线程来加载代表 CtClass 的类文件,并返 回一个代表加载类的 java.lang.Class 对象。更多详情,请见本章的下面说明。 定义一个新类 要定义一个新类,请使用 ClssPool 的 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 对象的后续修改都
是不允许的。这是为了警告那些试图修改已经被加载的类文件的开发者,因为 JVM 不允许再次加载同一个类。 一个冻结的 CtClass 对象可以被解冻,这样类定义的修改就被允许。例如: CtClasss cc = ...; : cc.writeFile(); cc.defrost(); cc.setSuperclass(...); // OK since the class is not frozen. 执行 defrost()方法后,CtClass 对象就可再次被修改。 如果 ClassPool.doPruning()方法设置为 true,Javassist 可以优化调整一个被 冻结的 CtClass 对象的数据结构。优化调整指的是为了减少内存使用,去除对象 内的一些不必要的属性(比如 attribute_info,方法体中的 Code_attribute)。 因此,当一个 CtClass 对象被优化调整后,一个方法的字节码除了方法名,方法 签名和注解外都是不可访问的。优化后的 CtClass 对象不能被再次解冻。 ClassPool.doPruning()方法默认值为 false。 对一个 CtClass 对象上执行 stopPruning()方法,可防止其优化调整: CtClasss cc = ...; cc.stopPruning(true); : cc.writeFile(); file. // cc is not pruned. // convert to a class CtClass 对象 cc 不会被优化。这样,在调用 writeFile()后还可以被解冻。
注意:你可能想在调试时暂时不优化并冻结对象,以便于将一个改变了的 类文件写入磁盘中。debugWriteFile()方法可以方便的达到这个目的。它 会先停止优化调整,写入一个 class 文件,再解冻这个对象,并且再次打 开优化开关(如果开始时是打开优化开关的)。 类搜索路径 静态方法 ClassPool.getDefault() 返回的缺省 ClassPool 会搜索和当前 JVM 相同的搜索的路径。如果 程序是运行在譬如 JBoss 和 Tomcat 之类的 web 应用服 务器上,ClassPool 对象可能就找不到用户自己的类,这是由于 web 应用服务器 除了使 用系统类加载器之外,还使用其他多个类加载器。在这种情况下,额外 的类路径就需要注册到 ClassPool 中。假定 pool 是对 ClassPool 对象的 引用: pool.insertClassPath(new ClassClassPath(this.getClass())); 上面的语句将 this 对象对应的类路径注册进来。除了使用 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 这个类,class 文件可以这样获取: http://www.javassist.org:80/java/org/javassist/test/Main.class 另外,你还可以通过直接通过字节码构造的方式来获取 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 构造的。当 get()方法被调用时,ClassPool 通过给定的 ByteArrayClassPath 来读取类文件,这种方式和通过名称获取 CtClass 对象是相同的。 如果你不知道类的全限定名,你可以使用 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 是按需读取 class 文件,这样会造成对 jar 包 内每个文件的重复搜索。makeClass()能起到优化搜索的作用,因为通过 makeClass()方法构造的 CtClass 对象会保持在 ClassPool 对象中,而与之对应 的类文件不会被读取。
用户也可以扩展类搜索路径。你可以定义一个新的 ClassPath 接口实现类,并将 其实例通过 insertClassPath() 方法放入 ClassPool 中。这样允许非标准资源 加入到搜索路径中。 2. ClassPool 举个例子,假定一个新的 getter()方法被加入到类 Point 对应的 CtClass 对象 中。之后,程序需要编译含有 getter()方法的 Point 源码,并将编译代码加入 到另一个类 Line。如果代表 Point 的 CtClass 丢失的话,编译器就不能编译 getter()方法,因为原始的类定义并不包含 getter()方法。因此,要正确的编 译一个方法调用,ClassPool 一定需要包含执行期间所有的 CtClass 对象。 避免内存溢出 当 CtClass 对象很大时(这种情况很少发生,因为 Javassist 会通过各种方式减 少内存消耗),对应的 ClassPool 就会消耗大量内存。要避免这种情况发生,你 可以显式的删除不必要的 CtClass 对象。当你调用 CtClass 对象的 detach()方 法时,CtClass 对象就会从 ClassPool 中删除掉。例如: CtClass cc = ... ; cc.writeFile(); cc.detach(); 当 detach()方法调用后,你不能再调用 CtClass 对象的任何方法。不过,你可 以通过 ClassPool 的 get()方法获取一个新的实例。当你调用 get()方法时, ClassPool 会再次读取 class 文件并创建一个新的 CtClass 对象。 另一种方式是用新的 ClassPool 替代旧的 ClassPool。如果旧的 ClassPool 被垃 圾回收,ClassPool 中的 CtClass 对象也同样会被回收掉。创建新的 ClassPool 代码片段如下: ClassPool cp = new ClassPool(true);
// if needed, append an extra search path by appendClassPath() 这种方式创建的 ClassPool 和通过 ClassPool.getDefault() 获取的 ClassPool 行为一致。ClassPool.getDefault()只是个方便使用的单例模式。上述代码会创 建一个新的 ClassPool 对象。getDefault()方法获取的 ClassPool 并没有特别之 处,只是方便使用而已。 new ClassPool(true) 是个方便的构造器,它会将系统搜索路径加入到 ClassPool 对象中。上述构造方法和下面代码作用一样: ClassPool cp = new ClassPool(); cp.appendSystemPath(); // or append another path by appendClassPath() 层叠 ClassPool 如果程序是运行在 web 应用服务器上,就会有可能创建多个 ClassPool 实例;每 个 ClassPool 对应一个 ClassLoader。程序应该通过 ClassPool 的构造器而不是 getDefault()方法来创建 ClassPool 对象。 就像 java.lang.ClassLoader,ClassPool 之间也存在层叠关系。比如: ClassPool parent = ClassPool.getDefault(); ClassPool child = new ClassPool(parent); child.insertClassPath("./classes"); 当 child.get()调用时,子 ClassPool 会首先委派给父 ClassPool。当父 ClassPool 没找到这个类文件时,子 ClassPool 才会在 ./classes 目录下寻找 此类文件。 当设置 child.childFirstLookup 为 true 时,子 ClassPool 就会先于父 ClassPool 来寻找此类文件。比如: ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent); child.appendSystemPath(); // the same class path as the default one. child.childFirstLookup = true; // changes the behavior of the child. 通过改变类名来定义新类 一个新类可被定义为已有类的拷贝。程序如下: ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); cc.setName("Pair"); 上面程序首先获取 Point 的 CtClass 对象。之后这个 CtClass 对象通过 setName() 方法调用被赋予新名称 Pair。从此,CtClass 对象所代表的类类名就从 Point 变更为 Pair。类定义的其他部分则保持不变。 CtClass 的 setName()方法修改了 ClassPool 对象的映射记录。从实现的角度看, ClassPool 对象是 CtClass 对象的哈希表。setName()方法改变了 CtClass 对象 在此哈希表中的 key 关联。key 从原先的类名变更为新的类名。 因此,当 ClassPool 对象的 get("Point")方法再次调用,不会将 CtClass 对象 返回给 cc 变量。ClassPool 对象会再次读取 Point.class 文件并重新构造一个 新的 Point CtClass 对象,而之前关联 Point 的 CtClass 对象已经不存在了。 如下: ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("Point"); CtClass cc1 = pool.get("Point"); // cc1 is identical to cc. cc.setName("Pair"); CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
分享到:
收藏