[置顶] ASM(五) 利用TreeApi 解析生成及转换Class


       前面CoreApi的介绍部分基本涵盖了ASMCore包下面的主要API及功能,其中还有一部分关于MetaData的解析和生成就不再赘述。这篇开始介绍ASM另一部分主要的Api。TreeApi。这一部分源码是关联的asm-tree-5.0.4的版本。

       在介绍前,先要知道一点, Tree工程的接口基本可以完成大部分我们之前介绍的Core中的功能。但是在实际使用中更加便利,当然也会更加消耗时间和性能。完成一个简单的生成编译后的Class字节码的任务,可能会花费多余Core的30%的时间,同时也会消耗更多内存。但是通过下面的介绍,相信在选择用哪种Api上,我们也会做出自己的取舍和判断。

一、生成编译后的Class


生成和转换一个编译后的Java类在TreeApi 中,主要借助ClassNode来完成。我们先一览一下ClassNode的一部分内容。

public class ClassNode  extends ClassVisitor {
public int version;
public int access;
public String name;
public String signature;
public String superName;
public List<String> interfaces;
public String sourceFile;
public String sourceDebug;
public String outerClass;
public String outerMethod;
public String outerMethodDesc;
public List<AnnotationNode> visibleAnnotations;
public List<AnnotationNode> invisibleAnnotations;
public List<Attribute> attrs;
public List<InnerClassNode> innerClasses;
public List<FieldNode> fields;
public List<MethodNode> methods;
…
}

同样,也有FieldNode 和MethodNode。这两个Api后续再详细介绍。可以看出ClassNode的这些公有成员可以让我们直接访问,可以通过初始化这些成员来生成编译后的Class,而不是像ClassVisitor那样,只能调用visit、visitField等方法来实现。下面简单看个例子,这个例子和之前我们那篇http://blog.csdn.net/lijingyao8206/article/details/46430403中一样,只是使用的API不同:

package asm.tree;
 
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
 
/**
 * Created by yunshen.ljy on 2015/7/12.
 */
public class GenerateClasses {
 
    public static void main(String[] args) throws FileNotFoundException {
        ClassWriter cw = new ClassWriter(Opcodes.ASM5);
        ClassNode cn = gen();
        cn.accept(cw);
        File file = new File("ChildClass.class");
        FileOutputStream fout = new FileOutputStream(file);
        try {
            fout.write(cw.toByteArray());
            fout.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
 
    private static ClassNode gen() {
        ClassNode classNode = new ClassNode();
        classNode.version = Opcodes.V1_8;
        classNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT;
        classNode.name = "asm/core/ChildClass";
        classNode.superName = "java/lang/Object";
        classNode.interfaces.add("asm/core/ParentInter");
        classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "zero", "I",
                null, new Integer(0)));
        classNode.methods.add(new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "compareTo",
                "(Ljava/lang/Object;)I", null, null));
        return classNode;
 
    }
}
 

上述例子,我们借助ClassWriter来输出字节数组是为了方便查看和对比生成的Class文件和我们之前用ClassVisitor是否达到同样的效果。ClassNode 的accept方法在第三部分展开介绍一下。同时,我们注意到,因为我们是可以直接访问类的共有变量的,所以也就不需要像ClassVisitor那样讲究调用顺序。用ClassNode 生成各个元素可以是无序访问,在一些情况确实是遍历,当然损失的就是性能。

二、添加和移除类成员

       同生成的做法一样,添加和移除类成员只要去修改fields和methods中的元素即可。这里我们拿一个简单的类做例子,下面这个Task类,我们来移除isNeedRemove方法,并且添加一个int 类型的addedField属性。

package asm.core;
 
/**
 * Created by yunshen.ljy on 2015/6/8.
 */
public class Task {
 
    private int isTask = 0;
 
    public void tellMe(){
        System.out.println("call tellMe");
    }
 
    public void isNeedRemove(){
        // do sth
    }
}

        首先我们可以构建一个Transformer用来处理ClassNode中的fields和methods列表。为了方便维护和扩展,我们创建两个Transformer。一个RemoveMethodTransformer来移除方法,一个AddFieldTransformer来添加field。

        读取Task的字节码,可以通过CoreApi 的ClassReader,而且因为ClassNode继承了ClassVisitor,所以我们可以通过ClassReader的accept方法来处理,这里依旧是将ClassReader当做一个生产者,ClassNode是消费者。而且,有趣的是,ClassNode也有一个accept方法,也就是说,可以传递本身的事件给一个ClassVisitor。这个ClassVisitor会继续“消费”所有ClassNode 属性中的事件。

        先来看一下RemoveMethodTransformer的实现:

package asm.tree;
 
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
 
import java.util.Iterator;
 
/**
 * Created by yunshen.ljy on 2015/7/12.
 */
public class RemoveMethodTransformer {
    private String fieldName;
    private String fieldDesc;
 
    public RemoveMethodTransformer(String fieldName, String fieldDesc) {
        this.fieldName = fieldName;
        this.fieldDesc = fieldDesc;
    }
 
    public void transform(ClassNode cn) {
        Iterator<MethodNode> i = cn.methods.iterator();
        while (i.hasNext()) {
            MethodNode mn = i.next();
            if (mn.name.equals(fieldName) ) {
                i.remove();
            }
        }
    }
}
 

可以看到transform方法利用迭代器来直接处理ClassNode对象的methods列表。下面再看一下AddFieldTransformer的实现。

package asm.tree;
 
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
 
/**
 * Created by yunshen.ljy on 2015/7/12.
 */
public class AddFieldTransformer   {
    private int fieldAccess;
    private String fieldName;
    private String fieldDesc;
 
    public AddFieldTransformer(int fieldAccess, String fieldName, String fieldDesc) {
        this.fieldAccess = fieldAccess;
        this.fieldName = fieldName;
        this.fieldDesc = fieldDesc;
    }
 
    public void transform(ClassNode cn) {
        boolean isPresent = false;
        for (Object fn : cn.fields) {
            FieldNode ff = (FieldNode) fn;
            if (fieldName.equals(ff.name)) {
                isPresent = true;
                break;
            }
        }
        if (!isPresent) {
            cn.fields.add(new FieldNode(fieldAccess, fieldName, fieldDesc, null, null));
        }
    }
 
}

最后,测试并利用ClassWriter来输出处理后的二进制字节流。我们刚好可以利用ClassNode的accept方法来让ClassWriter 能够Visit整个class,并且输出字节数组。 

        ClassReader cr = new ClassReader("asm.core.Task");
        ClassNode cn = new ClassNode();
        cr.accept(cn,0);
        RemoveMethodTransformer rt = new RemoveMethodTransformer("isNeedRemove","V");
        rt.transform(cn);
        AddFieldTransformer at= new AddFieldTransformer(Opcodes.ACC_PRIVATE,"addedField","I");
        at.transform(cn);
        ClassWriter cw = new ClassWriter(0);
        cn.accept(cw);
        byte[] toByte = cw.toByteArray();

当然,也可以自己写一个Adapter,来封装ClassNode的处理。通过构造器来传递ClassWriter,如下,将accept方法封装到visitEnd方法中,来绑定下一个消费事件:

public class MyClassAdapter extends ClassNode {
 
    public MyClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4);
        this.cv = cv;
    }
 
    @Override
    public void visitEnd() {
        accept(cv);
    }
}
或者再增加一层代理来传递:

package asm.tree;
 
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
 
/**
 * Created by yunshen.ljy on 2015/7/14.
 */
public class MyClassAdapter extends ClassVisitor {
 
    private ClassVisitor next;
    public MyClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4,new ClassNode());
        this.next = cv;
    }
 
    @Override
    public void visitEnd() {
        ClassNode cn = (ClassNode)cv;
        cn.accept(cv);
    }
}


你可能感兴趣的:(java,jvm,字节码,Class文件,ASM)