首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单介绍ASM核心API

简单介绍ASM核心API

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

核心类是 ClassVisitor

public abstract class ClassVisitor {
    public ClassVisitor(int api) {}
    public ClassVisitor(int api, ClassVisitor classVisitor) {}
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {}
    public void visitSource(String source, String debug) {}
    public ModuleVisitor visitModule(String name, int access, String version) {}
    public void visitNestHost(String nestHost) {}
    public void visitOuterClass(String owner, String name, String descriptor) {}
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {}
    public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {}
    public void visitAttribute(Attribute attribute) {}
    public void visitNestMember(String nestMember) {}
    public void visitInnerClass(String name, String outerName, String innerName, int access) {}
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {}
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {}
    public void visitEnd() {}
}

它的调用顺序是首先调用 visit,然后是对 visitSource 的最多一个调用,接下来是对 visitOuterClass 的最多一个调用,然后是可按任意顺序对 visitAnnotation 和 visitAttribute 的任意多个访问,接下来是可按任意顺序对 visitInnerClass、 visitField 和 visitMethod 的任意多个调用,最后以一个 visitEnd 调用结束。

ASM 提供了三个基于 ClassVisitor API 的核心组件,用于生成和变化类:

  • ClassReader类分析以字节数组形式给出的已编译类,并针对在其accept方法参数中传送的ClassVisitor实例,调用相应的visitXxx方法。这个类可以看作一个事件产生器。
  • ClassWriter类是ClassVisitor抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用toByteArray方法来?取。这个类可以看作一个事件使用器。
  • ClassVisitor类将它收到的所有方法调用都委托给另一个ClassVisitor类。这个类可以看作一个事件筛选器。

读取类

在分析一个已经存在的类时,惟一必需的组件是ClassReader组件.以下内容就是用来打印一个类的内容的(简单化了的)

public class ClassPrinter extends ClassVisitor {
    public ClassPrinter() {
        super(ASM7);
    }

    public ClassPrinter(ClassVisitor cv) {
        super(ASM7, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        Log.e("ClassVisitor", name + " extends " + superName + " {");
    }

    @Override
    public void visitSource(String source, String debug) {
        super.visitSource(source, debug);
    }

    @Override
    public ModuleVisitor visitModule(String name, int access, String version) {
        return super.visitModule(name, access, version);
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
        super.visitOuterClass(owner, name, desc);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
        return super.visitTypeAnnotation(typeRef, typePath, desc, visible);
    }

    @Override
    public void visitAttribute(Attribute attr) {
        super.visitAttribute(attr);
    }

    @Override
    public void visitInnerClass(String name, String outerName, String innerName, int access) {
        super.visitInnerClass(name, outerName, innerName, access);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        Log.e("ClassVisitor", " " + desc + " " + name);
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        Log.e("ClassVisitor", " " + name + desc);
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
        super.visitEnd();
        Log.e("ClassVisitor", "}");
    }
}

接下来可以将这个 ClassPrinter 与一个 ClassReader 组件合并在一起,使 ClassReader 产生的事件由我们的 ClassPrinter 使用

        ClassPrinter cp = new ClassPrinter();
        ClassReader cr = new ClassReader(Runnable.class.getName()); 
        cr.accept(cp, 0);

这是java的写法.注意ClassReader构造函数,Android写法不一样.Android的apk里面不会自己带有Runnable.class文件,所以需要如下做

        ClassPrinter cp = new ClassPrinter();
        File srcFile = new File(Environment.getExternalStorageDirectory() + "/TestRunnable.class");
        ClassReader cr = new ClassReader(new FileInputStream(srcFile));
        cr.accept(cp, 0);

生成类

        //写文件
        String className = "com/example/XXX/asmdemo/Comparable";//最后一个挨着class名字必须是斜杠,否则生成的class文件不会默认import
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
                className, null, "java/lang/Object", null);
        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();
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File sdCardDir = Environment.getExternalStorageDirectory();
            File saveFile = new File(sdCardDir, "Comparable.class");//必须是.class结尾,否则放IDE里面是乱码
            FileOutputStream outStream = new FileOutputStream(saveFile);
            outStream.write(b);
            outStream.close();
        }

最终把生成的class文件adb pull出来放AS里面查看如下

package com.example.XXX.asmdemo;
public interface Comparable {
    int LESS = -1;
    int EQUAL = 0;
    int GREATER = 1;
    int compareTo(Object var1);
}

转化类

可以通过ClassVisitor进行属性转化

        byte[] b1 = cw.toByteArray();

        ClassReader cr = new ClassReader(b1);
        ClassWriter cw2 = new ClassWriter(0);//优化方法,加入cr------ClassWriter(cr,0)
        // cv 将所有事件转发给 cw
        ChangeVersionAdapter ca = new ChangeVersionAdapter(cw2);
        cr.accept(ca, 0);
        byte[] b2 = cw.toByteArray(); // b2 与 b1 表示同一个类

    public class ChangeVersionAdapter extends ClassVisitor {
        public ChangeVersionAdapter(ClassVisitor cv) {
            super(ASM7, cv);
        }

        @Override
        public void visit(int version, int access, String name,
                          String signature, String superName, String[] interfaces) {
            cv.visit(V1_7, access, name, signature, superName, interfaces);//就修改了java版本号
        }
    }

但这么做有个问题,上面的流程中整个 b1 均被分析,并 利用相应的事件从头从头构建了 b2,这种做法的效率不是很高。如果将 b1 中不被转换的部分 直接复制到 b2 中,不对其分析,也不生成相应的事件,其效率就会高得多

优化方法

在ClassReader组件的accept方法参数中传送了ClassVisitor,如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter,这意味着这个方法的内容将不会被转换,事实上,应用程序甚至不会看到其内容。在这种情况下,ClassReader组件不会分析这个方法的内容,不会生成相应事件,只是复制 ClassWriter中表示这个方法的字节数组。 缺点:对于那些增加字段、方法或指令的转换来说,这一点不 成问题,但对于那些要移除或重命名许多类成员的转换来说,这一优化将导致类文件大于未优化 时的情况。因此,仅对“增加性”转换应用这一优化。

移除类成员

public class RemoveMemberAdapter extends ClassVisitor {
    private String name;
    private String desc;

    public RemoveMemberAdapter(int api, String name, String desc) {
        super(api);
        this.name = name;
        this.desc = desc;
    }

    public RemoveMemberAdapter(int api, ClassVisitor classVisitor, String name, String desc) {
        super(api, classVisitor);
        this.name = name;
        this.desc = desc;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        if (this.name.equals(name) && this.desc.equals(descriptor)) {
            // 不要委托至下一个访问器 -> 这样将移除该方法
            return null;
        }
        return super.visitMethod(access, name, descriptor, signature, exceptions);
    }
}

增加类成员

如果要向一个类中添加一个字段,必须在原方法调用之间添加对 visitField 的一 个新调用,而且必须将这个新调用放在类适配器的一个访问方法中。比如,不能在 visit 方法 中这样做,因为这样可能会导致对 visitField 的调用之后跟有 visitSource、 visitOuterClass、visitAnnotation 或 visitAttribute,这是无效的。出于同样的原因,不能将这个新调用放在 visitSource、visitOuterClass、visitAnnotation 或 visitAttribute 方法中. 仅有的可能位置是 visitInnerClass、visitField、 visitMethod 或 visitEnd 方法。 事实上,惟一真正正确的解决方案是在 visitEnd 方法中添加更多调用,以添加新成员。实际上, 一个类中不得包含重复成员,要确保一个新成员没有重复成员,惟一方法就是将它与所有已有成员进行对 比,只有在 visitEnd 方法中访问了所有这些成员后才能完成这一工作。这种做法是相当受限制的。在 实践中,使用程序员不大可能使用的生成名,比如_counter$或4B7F i就足以避免重复成员了, 并不需要将它们添加到 visitEnd 中。

public class AddFieldAdapter extends ClassVisitor {
    private ClassVisitor classVisitor;
    private int fAcc;
    private String fName;
    private String fDesc;
    private boolean isFieldPresent;

    public AddFieldAdapter(ClassVisitor classVisitor, int fAcc, String fName, String fDesc) {
        super(ASM7);
        this.classVisitor = classVisitor;
        this.fAcc = fAcc;
        this.fName = fName;
        this.fDesc = fDesc;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        if (name.equals(fName) && this.fDesc.equals(descriptor)) {
            isFieldPresent = true;
        }
        return classVisitor.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        if (!isFieldPresent) {
            FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
            if (fv != null) {
                fv.visitEnd();
            }
        }
        classVisitor.visitEnd();
    }
}

谈谈MethodVisitor

下面将介绍一下如何通过MethodVisitor来生成一个方法.有如下一个类

    package pkg;
    public class Bean {
        private int f;
        public int getF() {
            return this.f; 
        }
    }

getter 方法的字节代码为:

    ALOAD 0
    GETFIELD pkg/Bean f I
    IRETURN

那么生成getter方法的步骤如下

mv.visitCode();
mv.visitVarInsn(ALOAD, 0); 
mv.visitFieldInsn(GETFIELD, "pkg/Bean", "f", "I"); 
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();

第一个调用启动字节代码的生成过程。然后是三个调用,生成这一方法的三条指令(可以看出,字节代码与 ASM API 之间的映射非常简单)。对 visitMaxs 的调用必须在已经访问了所有指令后执行。它用于为这个方法的执行帧定义局部变量和操作数栈部分的大小。最后一次调用用于结束此方法的生成过程。 为什么visitMaxs分别是1呢?这个和基于栈的指令集有关,相关内容参考 深入理解 JAVA 虚拟机(十)基于栈的字节码解释执行引擎 Java字节码的介绍

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 读取类
  • 生成类
  • 转化类
  • 移除类成员
  • 增加类成员
  • 谈谈MethodVisitor
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档