Asm 详解

Asm 是什么?

The ASM1
library was therefore designed to
work on compiled Java classes. It was also designed to be as fast and as small
as possible. Being as fast as possible is important in order not to slow down
too much the applications that use ASM at runtime, for dynamic class generation or transformation. And being as small as possible is important in order
to be used in memory constrained environments, and to avoid bloating the
size of small applications or libraries using ASM.

Asm 是用于动态改变或者生成class 文件的。在某些领域是非常方便的。

Asm 核心APi

ClassReader : 去读class 文件,包括从文件流里面读取。
ClassWriter :生成class 文件
ClassVisitor : 用于读取class 文件的时候,把class 文件里面的字段,方法,传递给你。就像xml 解析一样。

ASM provides three core components based on the ClassVisitor API to
generate and transform classes:
• The ClassReader class parses a compiled class given as a byte array,
and calls the corresponding visitXxx methods on the ClassVisitor
instance passed as argument to its accept method. It can be seen as an
event producer.
• The ClassWriter class is a subclass of the ClassVisitor abstract class
that builds compiled classes directly in binary form. It produces as
output a byte array containing the compiled class, which can be retrieved
with the toByteArray method. It can be seen as an event consumer.
• The ClassVisitor class delegates all the method calls it receives to
another ClassVisitor instance. It can be seen as an event filter.

解析class 文件:

1.自己实现一个ClassVisitor,重写你关注的部分。比如:

public class ClassPrinter extends ClassVisitor {
    public ClassPrinter() {
        super(ASM4);
    }
    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("}");
    }
}

用ClassRead 去读取class 文件,交给ClassVisitor 处理。

ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);

这样就会调用到你自己的ClassVisitor 里面的一些方法,比如,
visitField(当读取到字段的时候)

Generating classes(生成class 文件)

比如我们想要写一个下面这样的class:

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

我们用ClassWriter 去写出了一个class 文件, cw.toByteArray(); 返回的就是二进制字节,也就是class 文件。

但是这样用ClassWrieter 去写class 很繁琐,而且很多java 字节码自己不知道怎么写,都要去查,那么怎么办呢?
可以用Asm 工具生成(生成class 文件的)Java 代码:

Asm 工具生成(生成class 文件的)Java 代码:

使用如下:

java -classpath asm-7.0.jar;asm-util-7.1.jar org.objectweb.asm.util.ASMifier Util.class

注意:
1.asm-7.0.jar;asm-util-7.1.jar 中间是用;号(分号分割)
2.Util.class 是你写的代码编译之后的class,必须在当前执行的路径下。
应该也支持绝对路径。如:java -classpath asm-7.0.jar;asm-util-7.1.jar org.objectweb.asm.util.ASMifier D:\Util.class
3.如果你的class 是经过Android 编译生成的,但是你执行这个命令不需要把android 的classPath 加进来。他只会class
文件进行了识别生成代码。
asm Jar下载:
https://download.****.net/download/u013270444/11169356

比如:源代码是这样写的:

    public static boolean fileIsExist(String filePathName) {
        if (TextUtils.isEmpty(filePathName)) return false;
        File file = new File(filePathName);
        return (!file.isDirectory() && file.exists());
    }

我们编译一下,生成class文件

然后执行Asm 的工具方法之后,命令行输入的代码是:

java -classpath asm-7.0.jar;asm-util-7.1.jar org.objectweb.asm.util.ASMifier Util.class

执行完之后,会输出下面这些代码,就是生成你想要生成class 的Asm java代码。

{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "fileIsExist", "(Ljava/lang/String;)Z", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(36, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESTATIC, "android/text/TextUtils", "isEmpty", "(Ljava/lang/CharSequence;)Z", false);
Label label1 = new Label();
methodVisitor.visitJumpInsn(IFEQ, label1);
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitInsn(IRETURN);
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(37, label1);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitTypeInsn(NEW, "java/io/File");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/io/File", "<init>", "(Ljava/lang/String;)V", false);
methodVisitor.visitVarInsn(ASTORE, 1);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(38, label2);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/File", "isDirectory", "()Z", false);
Label label3 = new Label();
methodVisitor.visitJumpInsn(IFNE, label3);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/File", "exists", "()Z", false);
methodVisitor.visitJumpInsn(IFEQ, label3);
methodVisitor.visitInsn(ICONST_1);
Label label4 = new Label();
methodVisitor.visitJumpInsn(GOTO, label4);
methodVisitor.visitLabel(label3);
methodVisitor.visitFrame(Opcodes.F_APPEND,1, new Object[] {"java/io/File"}, 0, null);
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitLabel(label4);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {Opcodes.INTEGER});
methodVisitor.visitInsn(IRETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("filePathName", "Ljava/lang/String;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("file", "Ljava/io/File;", null, label2, label5, 1);
methodVisitor.visitMaxs(3, 2);
methodVisitor.visitEnd();
}

生成之后的截图:
Asm 详解
这样你就可以在某些地方,复制粘贴这些代码,运行的时候,就会生成你需要的class文件。

资料:

1.Asm 官网:
https://asm.ow2.io/index.html
2.Asm 下载地址
https://repository.ow2.org/nexus/#nexus-search;gavorg.ow2.asmasm~~~
3.Jvm 的instruction (指令集)
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5