美文网首页
简单介绍ASM树API

简单介绍ASM树API

作者: 勇敢地追 | 来源:发表于2019-12-30 18:12 被阅读0次

什么是树API

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

生成类

        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列表中添加或删除元素

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 组件使用,从而初始化其字段

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();

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

  • 使用继承:
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);
    }
}
  • 使用委托
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 对象,却还没有看到与类的字节数组表示进行链接。方法转换的模式其实也有两种

  • 基于继承的模式
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);
    }
}
  • 基于委托的模式
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方法

    public void checkAndSetF(int f) {
        if (f >= 0) {
            this.f = f;
        } else {
            throw new IllegalArgumentException();
        }
    }

他对应的字节码如下

        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

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

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有很好的了解,必须对字节码有很好的了解

相关文章

  • 简单介绍ASM树API

    什么是树API 就是采用基于对象的模型,类可以用一个对象树表示,每个对象表示类的一部分,比如类本身、一个字段、一个...

  • 简单介绍ASM核心API

    核心类是 ClassVisitor 它的调用顺序是首先调用 visit,然后是对 visitSource 的最多一...

  • ASM Core Api 详解

    前言 前面一篇文章 ASM 简介 对 ASM 框架做了简单的介绍。 本篇文章主要对该框架的 Core Api 其中...

  • 42 - ASM之Class Transformation总结

    Class Transformation,从Core API的角度来说(第二个层次),我们介绍了asm.jar当中...

  • javassist、ASM 对比

    1、javassist是基于源码级别的API比基于字节码的ASM简单。2、基于javassist开发,不需要了解字...

  • ASM介绍

    ASM介绍 [TOC] 背景和痛点 你是否经历过下面那些让你万马奔腾的场景 当你在工程的代码中按照产品增加了埋点信...

  • 02 - ASM组成部分

    ASM的两个组成部分 从组成结构上来说,ASM分成两部分,一部分为Core API,另一部分为Tree API。 ...

  • 43 - ASM之asm-util和asm-commons

    asm-util 在asm-util.jar当中,主要介绍CheckClassAdapter和TraceClass...

  • iOS 渲染滤镜

    前言 介绍几种简单的滤镜, image API & Property 简单介绍思路 KJRenderInfo滤镜模...

  • Android隐私合规

    目录介绍 前言 合规检查检测隐私API方案 合规整改 合规解决方案-ASM插桩什么是ASMASM 插桩可以做什么?...

网友评论

      本文标题:简单介绍ASM树API

      本文链接:https://www.haomeiwen.com/subject/mrkinctx.html