前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单介绍ASM树API

简单介绍ASM树API

作者头像
提莫队长
发布2020-06-02 15:28:28
9690
发布2020-06-02 15:28:28
举报
文章被收录于专栏:刘晓杰刘晓杰

什么是树API

就是采用基于对象的模型,类可以用一个对象树表示,每个对象表示类的一部分,比如类本身、一个字段、一个方法、一条指令,等等,每个对象都有一些引用,指向表示其组成部分的对象。

生成类

代码语言:javascript
复制
        ClassNode cn = new ClassNode();
        cn.version = V1_8;
        cn.access = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE;
        cn.name = "pkg/Comparable";
        cn.superName = "java/lang/Object";
        cn.interfaces.add("pkg/Mesurable");
        cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
                "LESS", "I", null, new Integer(-1)));
        cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
                "EQUAL", "I", null, new Integer(0)));
        cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
                "GREATER", "I", null, new Integer(1)));
        cn.methods.add(new MethodNode(ACC_PUBLIC + ACC_ABSTRACT,
                "compareTo", "(Ljava/lang/Object;)I", null, null));

使用树API生成类时,需要多花费大约30%的时间,占用的内存也多于使用核心API。但可以按任意顺序生成类元素,这在一些情况下可能非常方便。

添加和删除类成员

添加和删除类就是在ClassNode对象的fields或methods列表中添加或删除元素

代码语言:javascript
复制
public class ClassTransformer {
    protected ClassTransformer ct;

    public ClassTransformer(ClassTransformer ct) {
        this.ct = ct;
    }

    public void transform(ClassNode cn) {
        if (ct != null) {
            ct.transform(cn);
        }
    }
}

public class RemoveMethodTransformer extends ClassTransformer {
    private String methodName;
    private String methodDesc;

    public RemoveMethodTransformer(ClassTransformer ct, String methodName, String methodDesc) {
        super(ct);
        this.methodName = methodName;
        this.methodDesc = methodDesc;
    }

    @Override
    public void transform(ClassNode cn) {//重点在这里
        Iterator<MethodNode> i = cn.methods.iterator();
        while (i.hasNext()) {
            MethodNode mn = i.next();
            if (methodName.equals(mn.name) && methodDesc.equals(mn.desc)) {
                i.remove();
            }
        }
        super.transform(cn);
    }
}

public class AddFieldTransformer extends ClassTransformer {
    private int fieldAccess;
    private String fieldName;
    private String fieldDesc;

    public AddFieldTransformer(ClassTransformer ct, int fieldAccess, String fieldName, String fieldDesc) {
        super(ct);
        this.fieldAccess = fieldAccess;
        this.fieldName = fieldName;
        this.fieldDesc = fieldDesc;
    }

    @Override
    public void transform(ClassNode cn) {//重点在这里
        boolean isPresent = false;
        for (FieldNode fn : cn.fields) {
            if (fieldName.equals(fn.name)) {
                isPresent = true;
                break;
            }
        }
        if (!isPresent) {
            cn.fields.add(new FieldNode(fieldAccess, fieldName, fieldDesc, null, null));
        }
        super.transform(cn);
    }
}

它与核心 API 的主要区别是需要迭代所有方法,而在使用核心 API 时是不需要这样做的(这一工作会在 ClassReader 中为你完成)。事实上,这一区别对于几乎所有基于树的转换都是有效的。 和生成类的情景一样,使用树 API 转换类时,所花费的时间和占用的内存也要多于使用核 心 API 的时候。但使用树 API 有可能使一些转换的实现更为容易。比如有一个转换,要向一个类中添加注释,包含其内容的数字签名,就属于上述情景。在使用核心 API 时,只有在访问了 整个类之后才能计算数字签名,但这时再添加包含其内容的注释就太晚了,因为对注释的访问必须位于类成员之前。而在使用树 API 时,这个问题就消失了,因为这时不存在此种限制。

类的生成和转化

上面只是如何创建和转换 ClassNode 对象,但还没有看到如何由一个类的字节数组表示来构造一个 ClassNode,或者反过来,由 ClassNode 构造这个字节数组。要由字节数组构建 ClassNode,可以将它与 ClassReader 合在一起,使 ClassReader 生成的事件可供 ClassNode 组件使用,从而初始化其字段

代码语言:javascript
复制
ClassNode cn = new ClassNode(ASM4); 
ClassReader cr = new ClassReader(...); 
cr.accept(cn, 0);
... // 可以在这里根据需要转换 cn
ClassWriter cw = new ClassWriter(0); 
cn.accept(cw);
byte[] b = cw.toByteArray();

类转换器有两种常见模式。

  • 使用继承:
代码语言:javascript
复制
public class MyClassAdapter extends ClassNode {
    public MyClassAdapter(ClassVisitor cv) {
        super(ASM7);
        this.cv = cv;
    }

    @Override
    public void visitEnd() {
        // put your transformation code here 
        accept(cv);
    }
}
  • 使用委托
代码语言:javascript
复制
public class MyClassAdapter extends ClassVisitor {
    ClassVisitor next;

    public MyClassAdapter(ClassVisitor cv) {
        super(ASM7, new ClassNode());
        next = cv;
    }

    @Override
    public void visitEnd() {
        ClassNode cn = (ClassNode) cv;
        // 将转换代码放在这里
        cn.accept(next);
    }
}

方法转换

到目前为止,我们仅看到了如何创建和转换 MethodNode 对象,却还没有看到与类的字节数组表示进行链接。方法转换的模式其实也有两种

  • 基于继承的模式
代码语言:javascript
复制
public class MyMethodAdapter extends MethodNode {
    public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) {
        super(ASM7, access, name, desc, signature, exceptions);
        this.mv = mv;
    }

    @Override
    public void visitEnd() {
        // 将你的转换代码放在这儿
        accept(mv);
    }
}
  • 基于委托的模式
代码语言:javascript
复制
public class MyMethodAdapter extends MethodVisitor {
    MethodVisitor next;

    public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) {
        super(ASM7, new MethodNode(access, name, desc, signature, exceptions));
        next = mv;
    }

    @Override
    public void visitEnd() {
        MethodNode mn = (MethodNode) mv;
        //将你的转换代码放在这儿
        mn.accept(next);
    }
}

这些模式表明,可以将树 API 仅用于方法,将核心 API 用于类。在实践中经常使用这一策略。

用MethodNode生成一个方法

假设有如下java方法

代码语言:javascript
复制
    public void checkAndSetF(int f) {
        if (f >= 0) {
            this.f = f;
        } else {
            throw new IllegalArgumentException();
        }
    }

他对应的字节码如下

代码语言:javascript
复制
        ILOAD 1
        IFLT label
        ALOAD 0
        ILOAD 1
        PUTFIELD pkg/Bean f I 
        GOTO end
label:
        NEW java/lang/IllegalArgumentException
        DUP
        INVOKESPECIAL java/lang/IllegalArgumentException <init> ()V
        ATHROW
end:
        RETURN

那么对照字节码可以写出如下的生成代码

代码语言:javascript
复制
MethodNode mn = new MethodNode(...);
InsnList il = mn.instructions;
il.add(new VarInsnNode(ILOAD, 1));
LabelNode label = new LabelNode();
il.add(new JumpInsnNode(IFLT, label));
il.add(new VarInsnNode(ALOAD, 0));
il.add(new VarInsnNode(ILOAD, 1));
il.add(new FieldInsnNode(PUTFIELD, "pkg/Bean", "f", "I")); 
LabelNode end = new LabelNode();
il.add(new JumpInsnNode(GOTO, end));
il.add(label);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new TypeInsnNode(NEW, "java/lang/IllegalArgumentException")); 
il.add(new InsnNode(DUP));
il.add(new MethodInsnNode(INVOKESPECIAL,"java/lang/IllegalArgumentException", "<init>", "()V")); 
il.add(new InsnNode(ATHROW));
il.add(end);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new InsnNode(RETURN)); 
mn.maxStack = 2; 
mn.maxLocals = 2;

由此可见,要想对ASM的树API有很好的了解,必须对字节码有很好的了解

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是树API
  • 生成类
  • 添加和删除类成员
  • 类的生成和转化
  • 方法转换
  • 用MethodNode生成一个方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档