zoukankan      html  css  js  c++  java
  • [转载]Javassist 使用指南(三)

    =======================

    本文转载自简书,感谢原作者!。

    原链接如下:https://www.jianshu.com/p/7803ffcc81c8

    =======================

    5. 字节码操作

    Javassist 还提供了用于直接编辑类文件的低级级 API。 使用此 API之前,你需要详细了解Java 字节码和类文件格式,因为它允许你对类文件进行任意修改。

    如果你只想生成一个简单的类文件,使用javassist.bytecode.ClassFileWriter就足够了。 它比javassist.bytecode.ClassFile更快而且更小。

    获取 ClassFile 对象

    javassist.bytecode.ClassFile 对象表示类文件。要获得这个对象,应该调用 CtClass 中的 getClassFile() 方法。
    你也可以直接从类文件构造 javassist.bytecode.ClassFile 对象。 例如:

    BufferedInputStream fin
        = new BufferedInputStream(new FileInputStream("Point.class"));
    ClassFile cf = new ClassFile(new DataInputStream(fin));
    

    这代码段从 Point.class 创建一个 ClassFile 对象。
    ClassFile 对象可以写回类文件。ClassFile 的 write() 将类文件的内容写入给定的 DataOutputStream。

    5.2 添加和删除成员

    ClassFile 提供了 addField(),addMethod() 和 addAttribute(),来向类添加字段、方法和类文件属性。

    注意,FieldInfo,MethodInfo 和 AttributeInfo 对象包括到 ConstPool(常量池表)对象的链接。 ConstPool 对象必须对 ClassFile 对象和添加到该 ClassFile 对象的 FieldInfo(或MethodInfo 等)对象是通用的。 换句话说,FieldInfo(或MethodInfo等)对象不能在不同的ClassFile 对象之间共享。

    要从 ClassFile 对象中删除字段或方法,必须首先获取包含该类的所有字段的 java.util.List 对象。 getFields() 和 getMethods() 返回列表。可以通过在List对象上调用 remove() 来删除字段或方法。可以以类似的方式去除属性。在 FieldInfo 或 MethodInfo 中调用 getAttributes() 以获取属性列表,并从列表中删除一个。

    5.3 遍历方法体

    使用 CodeIterator 可以检查方法体中的每个字节码指令,要获得 CodeIterator 对象,参考以下代码:

    ClassFile cf = ... ;
    MethodInfo minfo = cf.getMethod("move");    // we assume move is not overloaded.
    CodeAttribute ca = minfo.getCodeAttribute();
    CodeIterator ci = ca.iterator();
    

    CodeIterator 对象允许你逐个访问每个字节码指令。下面展示了一部分 CodeIterator 中声明的方法:

    • void begin()
      移动到第一条指令。
    • void move(int index)
      移动到指定位置的指令。
    • boolean hasNext()
      是否有下一条指定
    • int next()
      返回下一条指令的索引。注意,它不返回下一条指令的操作码。
    • int byteAt(int index)
      返回索引处的无符号8位整数。
    • int u16bitAt(int index)
      返回索引处的无符号16位整数。
    • int write(byte [] code,int index)
      在索引处写入字节数组。
    • void insert(int index,byte [] code)
      在索引处插入字节数组。自动调整分支偏移量。

    以下代码段打印了方法体中所有的指令:

    CodeIterator ci = ... ;
    while (ci.hasNext()) {
        int index = ci.next();
        int op = ci.byteAt(index);
        System.out.println(Mnemonic.OPCODE[op]);
    }
    

    5.4 生成字节码序列

    Bytecode 对象表示字节码指令序列。它是一个可扩展的字节码数组。
    以下是示例代码段:

    ConstPool cp = ...;    // constant pool table
    Bytecode b = new Bytecode(cp, 1, 0);
    b.addIconst(3);
    b.addReturn(CtClass.intType);
    CodeAttribute ca = b.toCodeAttribute();
    

    这段代码产生以下序列的代码属性:

    iconst_3
    ireturn
    

    您还可以通过调用 Bytecode 中的 get() 方法来获取包含此序列的字节数组。获得的数组可以插入另一个代码属性。
    Bytecode 提供了许多方法来添加特定的指令,例如使用 addOpcode() 添加一个 8 位操作码,使用 addIndex() 用于添加一个索引。每个操作码的值定义在 Opcode 接口中。
    addOpcode() 和添加特定指令的方法,将自动维持最大堆栈深度,除非控制流没有分支。可以通过调用 Bytecode 的 getMaxStack() 方法来获得这个深度。它也反映在从 Bytecode对象构造的 CodeAttribute 对象上。要重新计算方法体的最大堆栈深度,可以调用 CodeAttribute 的 computeMaxStack() 方法。

    5.5 注释(元标签)

    注释作为运行时不可见(或可见)的注记属性,存储在类文件中。调用 getAttribute(AnnotationsAttribute.invisibleTag)方法,可以从 ClassFile,MethodInfo 或 FieldInfo 中获取注记属性。更多信息,请参阅 javassist.bytecode.AnnotationsAttributejavassist.bytecode.annotation 包的 javadoc 手册。

    Javassist还允许您通过更高级别的API访问注释。 如果要通过CtClass访问注释,请在CtClass或CtBehavior中调用getAnnotations()。

    6. 泛型

    Javassist 的低级别 API 完全支持 Java 5 引入的泛型。但是,高级别的API(如CtClass)不直接支持泛型。

    Java 的泛型是通过擦除技术实现。 编译后,所有类型参数都将被删除。 例如,假设您的源代码声明一个参数化类型 Vector<String>:

    Vector<String> v = new Vector<String>();
      :
    String s = v.get(0);
    

    编译后的字节码等价于以下代码:

    Vector v = new Vector();
      :
    String s = (String)v.get(0);
    

    因此,在编写字节码变换器时,您可以删除所有类型参数,因为 Javassist 的编译器不支持泛型。如果源代码使用 Javassist 编译,例如通过 CtMethod.make(),源代码必须显式类型转换。如果源代码由常规 Java 编译器(如javac)编译,则不需要做类型转换。

    例如,如果你有一个类:

    public class Wrapper<T> {
      T value;
      public Wrapper(T t) { value = t; }
    }
    

    并想添加一个接口 Getter<T> 到类 Wrapper<T>:

    public interface Getter<T> {
      T get();
    }
    

    那么你真正要添加的接口其实是Getter(将类型参数<T>掉落),最后你添加到 Wrapper 类的方法是这样的:

    public Object get() { return value; }
    

    注意,不需要类型参数。 由于 get 返回一个 Object,如果源代码是由 Javassist 编译的,那么在调用方需要进行显式类型转换。 例如,如果类型参数 T 是 String,则必须插入(String),如下所示:

    Wrapper w = ...
    String s = (String)w.get();
    

    7.可变参数

    目前,Javassist 不直接支持可变参数。 因此,要使用 varargs 创建方法,必须显式设置方法修饰符。假设要定义下面这个方法:

    public int length(int... args) { return args.length; }
    

    使用 Javassist 应该是这样的:

    CtClass cc = /* target class */;
    CtMethod m = CtMethod.make("public int length(int[] args) { return args.length; }", cc);
    m.setModifiers(m.getModifiers() | Modifier.VARARGS);
    cc.addMethod(m);
    

    参数类型int ...被更改为int []Modifier.VARARGS被添加到方法修饰符中。

    要在由 Javassist 的编译器编译的源代码中调用此方法,需要这样写:

    length(new int[] { 1, 2, 3 });
    

    而不是这样:

    length(1, 2, 3);
    

    8. J2ME

    如果要修改 J2ME 执行环境的类文件,则必须先执行预验证。预验证基本上是生成堆栈映射,这类似于在 JDK 1.6 中引入 J2SE 的堆栈映射表。当javassist.bytecode.MethodInfo.doPreverify 为 true 时,Javassist 才会维护 J2ME 的堆栈映射。

    对于指定的 CtMethod 对象,你可以调用以下方法,手动生成堆栈映射:

    m.getMethodInfo().rebuildStackMapForME(cpool);
    

    这里,cpool 是一个 ClassPool 对象,通过在 CtClass 对象上调用 getClassPool() 可以获得。 ClassPool 对象负责从给定类路径中查找类文件。要获得所有的 CtMethod 对象,需要在 CtClass 对象上调用 getDeclaredMethods() 方法。

    9.装箱/拆箱

    Java 中的装箱和拆箱是语法糖。没有用于装箱或拆箱的字节码。所以 Javassist 的编译器不支持它们。 例如,以下语句在 Java 中有效:

    Integer i = 3;
    

    因为隐式地执行了装箱。 但是,对于 Javassist,必须将值类型从 int 显式地转换为 Integer:

    Integer i = new Integer(3);
    

    10. 调试

    将 CtClass.debugDump 设为本地目录。 然后 Javassist 修改和生成的所有类文件都保存在该目录中。要停止此操作,将 CtClass.debugDump 设置为 null 即可。其默认值为 null。

    例如,

    CtClass.debugDump =“./dump”;
    

    所有修改的类文件都保存在 ./dump 中。

  • 相关阅读:
    jsp转向
    什么是 XDoclet?
    tomcat中的几点配置说明
    mysql5问题
    POJ 3734 Blocks
    POJ 2409 Let it Bead
    HDU 1171 Big Event in HDU
    POJ 3046 Ant Counting
    HDU 2082 找单词
    POJ 1286 Necklace of Beads
  • 原文地址:https://www.cnblogs.com/yeyang/p/10395995.html
Copyright © 2011-2022 走看看