Core API之Class接口和组件

来源:互联网 发布:阿里的数据平台两个 编辑:程序博客网 时间:2024/05/17 04:10

Presentation

    ASM生成和转换编译后class的API是基于ClassVisitor接口。简单部分通过简单方法调用(参数描述它们的内容,且返回类型为void)来访问。哪些内容长度不定且复杂的部分可以通过初始方法调用来访问,它返回附加的访问接口。visitAnnotation,visitField和visitMethod方法各自返回AnnotationVisitor、FieldVisitor和MethodVisitor。
public interface ClassVisitor {    void visit(int version, int access, String name, String signature,               String superName, String[] interfaces);    void visitSource(String source, String debug);    void visitOuterClass(String owner, String name, String desc);               AnnotationVisitor visitAnnotation(String desc, boolean visible);    void visitAttribute(Attribute attr);    void visitInnerClass(String name, String outerName, String innerName,int access);    FieldVisitor visitField(int access, String name, String desc,         String signature, Object value);    MethodVisitor visitMethod(int access, String name, String desc,         String signature, String[] exceptions);    void visitEnd();}public interface FieldVisitor {    AnnotationVisitor visitAnnotation(String desc, boolean visible);    void visitAttribute(Attribute attr);    void visitEnd();}

    ClassVisitor接口必须按以下顺序调用:visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd

    意味着,visit必须第一个被调用,紧接着最多调用一次visitSource,接着最多调用一次visitOuterClass,接着顺序调用多次visitAnnotation和visitAttribute,接着多次顺序调用visitInnerClass,visitFieldvisitMethod,调用最后visitEnd结束。ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。

    ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。

    CLassReader:将编译过的class转换为字节数组,且调用ClassVisitor实例的visitXxx方法,ClassVisitor作为参数传递给accept方法。可以看作是事件生产者。

    ClassWriter:ClassVisitor接口的一个实现,它以二进制格式直接构建编译后的class。它产生包含编译后的class的二进制字节数组作为输出。可以看作是事件消费者。

    ClassAdapter:ClassVisitor接口的一个实现,它委托所有来自另外一个ClassVisitor实例的方法调用。它可以看作是过滤器。

Parsing classes

    转换一个已存在的class需要唯一组件是ClassReader。假设我们要打印class的内容,类似反编译命令javap。第一步实现ClassVisitor接口来打印class信息。
public class ClassPrinter implements ClassVisitor {    public void visit(int version, int access, String name,                      String signature, String superName, String[] interfaces) {       System.out.println(name + " extends " + superName + " {");    }    public void visitSource(String source, String debug) {    }    public void visitOuterClass(String owner, String name, String desc) {    }    public AnnotationVisitor visitAnnotation(String desc,boolean visible) {       return null;    }    public void visitAttribute(Attribute attr) {    }    public void visitInnerClass(String name, String outerName,String innerName, int access) {    }    public FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {       System.out.println("" + desc + " " + name);       return null;    }    public MethodVisitor visitMethod(int access, String name,                         String desc, String signature, String[] exceptions) {       System.out.println("" + name + desc);       return null;    }    public void visitEnd() {       System.out.println("}");    }}
    第二步Classprinter与ClassReader一起使用,以便ClassReader产生的事件能被ClassPrinter来消费。    

ClassPrinter cp = new ClassPrinter();ClassReader cr = new ClassReader("java.lang.Runnable");cr.accept(cp, 0);
   accept方法在Runnable类字节码最后一行被转换后被调用,并且会调用cp上的ClassVisitor方法。  

Generating classes

生成Class的唯一组件是ClassWriter。
package pkg;public interface Comparable extends Mesurable {   int LESS = -1;   int EQUAL = 0;   int GREATER = 1;   int compareTo(Object o);}
ClassWriter cw = new ClassWriter(0);cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,     "pkg/Comparable", null, "java/lang/Object",     new String[] { "pkg/Mesurable" });cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",     null, new Integer(-1)).visitEnd();cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",     null, new Integer(0)).visitEnd();cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",     null, new Integer(1)).visitEnd();cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",     "(Ljava/lang/Object;)I", null, null).visitEnd();cw.visitEnd();byte[] b = cw.toByteArray();
    visit方法定义了class的头信息。V1_5是在ASM Opcodes接口中定义的常量,它指定了class的版本。ACC_XXX常量对应Java修饰符。这里我们指定它是public和abstract。接下来参数指明了类名、接口和父类。最后一个参数它扩增的接口数组。
    连续的调用visitField方法,用来定义三个接口域,第一个参数是修饰符,第二个参数是field的名称,第三个参数是field类型。第四个参数泛型,在这里没有使用泛型,故为null。最后一个参数是field的常量值,这个参数仅仅被常量域使用。这里没有annotation,调用visitEnd方法。 visitMethod调用被用来定义compareTo方法。第一个参数是修饰符,第二个是方法名称。第三个参数是方法的描述。第四个为泛型,最后一个参数为方法能抛出的异常数组。visitMethod方法返回MethodVisitor,用来定义方法的annotation、属性和最为重要的方法的代码。接着调用visitEnd方法返回MethodVisitor。
最后一次调用visitEnd用来通知cw类已经结束,调用toByteArray获取字节数组。
可以使用自定义ClassLoader来加载生成的class。

class MyClassLoader extends ClassLoader {    public Class defineClass(String name, byte[] b) {        return defineClass(name, b, 0, b.length);    }}

Transforming classes 

 目前为止,ClassReader和ClassWriter组件被单独使用。联合使用这两个组件:

    1)ClassReader生产的event直接传递给ClassWriter。

byte[] b1 = ...;ClassWriter cw = new ClassWriter();ClassReader cr = new ClassReader(b1);cr.accept(cw, 0);byte[] b2 = cw.toByteArray();//b2代表了与b1Class一样的类.

    2)在ClassReader和ClassWriter引入ClassAdapter
byte[] b1 = ...;ClasssWriter cw = new ClassWriter();ClassAdapter ca = new ClassAdapter(cw); // ca forwards all events to cwClassReader cr = new ClassReader(b1);cr.accept(ca, 0);byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
转换链:ClassReader->ClassAdapter->ClassWriter
public class ChangeVersionAdapter extends ClassAdapter {    public ChangeVersionAdapter(ClassVisitor cv) {      super(cv);    }    @Override    public void visit(int version, int access, String name,        String signature, String superName, String[] interfaces) {      cv.visit(V1_5, access, name, signature, superName, interfaces);    }}
通过修改visit方法的其他参数,你可以完成其他转换。

Using transformed classes

    转换过的class可以存储到磁盘上或使用ClassLoader加载,但是在一个ClassLoader内的class转换只能转换被该类的classLoader加载进来的类。如果你向转换所有class,你必须将转换置于ClassFileTransformer(定义在java.lang.instrument)内。

public static void premain(String agentArgs, Instrumentation inst) {    inst.addTransformer(new ClassFileTransformer() {       public byte[] transform(ClassLoader l, String name, Class c,                  ProtectionDomain d, byte[] b)       throws IllegalClassFormatException {                  ClassReader cr = new ClassReader(b);                  ClassWriter cw = new ClassWriter(cr, 0);                  ClassVisitor cv = new ChangeVersionAdapter(cw);                  cr.accept(cv, 0);                 return cw.toByteArray();       }   });}

Removing class members

    通过改变visitField和visitMethod方法中的access和name参数,就可以改变数据域和方法的访问修饰符或名字。这样作的效果就是数据域或方法被移除了。
本例子就是移除外部类、内部类和源文件名称。public class RemoveDebugAdapter extends ClassAdapter {    public RemoveDebugAdapter(ClassVisitor cv) {       super(cv);    }    @Override    public void visitSource(String source, String debug) {    }    @Override    public void visitOuterClass(String owner, String name, String desc) {    }    @Override    public void visitInnerClass(String name, String outerName,           String innerName, int access) {    }}下例移除了指定方法public class RemoveMethodAdapter extends ClassAdapter {    private String mName;    private String mDesc;    public RemoveMethodAdapter(       ClassVisitor cv, String mName, String mDesc) {          super(cv);          this.mName = mName;          this.mDesc = mDesc;    }    @Override    public MethodVisitor visitMethod(int access, String name,           String desc, String signature, String[] exceptions) {       if (name.equals(mName) && desc.equals(mDesc)) {       // do not delegate to next visitor -> this removes the method           return null;       }       return cv.visitMethod(access, name, desc, signature, exceptions);    }}

Adding class members

public class AddFieldAdapter extends ClassAdapter {    private int fAcc;    private String fName;    private String fDesc;    private boolean isFieldPresent;    public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName,           String fDesc) {        super(cv);        this.fAcc = fAcc;        this.fName = fName;        this.fDesc = fDesc;    }    @Override    public FieldVisitor visitField(int access, String name, String desc,           String signature, Object value) {        if (name.equals(fName)) {           isFieldPresent = true;        }        return cv.visitField(access, name, desc, signature, value);    }    @Override    public void visitEnd() {        if (!isFieldPresent) {           FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);           if (fv != null) {             fv.visitEnd();           }        }        cv.visitEnd();    }}



原创粉丝点击