JVM_ASM技术原理分析

在前面的文章中,我们分析了Class 这个字节码文件的格式,知道了字节码的作用,那么我们就可以直接生成字节码文件,加载到当前的 JVM 中运行,这个在AOP 场景中经常用到。
当然直接手动写字节码难度比较大,太过麻烦。这里就介绍一个非常重要也非常高效的字节码生成框架ASM

Java8Lambda 表达式,也是通过ASM动态生成类的。

一. ASM 介绍

ASM可以高效地生成.class字节码文件,分为两种方式:

  • 核心API : 流式读取字节码文件内容,并通过访问器的模式,将读取到内容数据暴露出去,类比解析XML文件中的SAX方式。
  • 树形API : 底层就是采用核心API ,只不过它将读取到的内容节点,保存在内存中,形成一个树形结构,类比解析XML文件中的DOM方式。

因此我们只要了解核心API 原理就行了,主要就是以下几个方面:

  • 各种访问器Visitor,用来接收到字节码中的各部分数据。
  • 各种写入器Writer,它们是Visitor子类,接收到数据,然后写入到字节数组中,生成一个字节码文件。
  • ClassReader ,它比较特殊,就是用来读取一个字节码文件,并且可以将字节码中的各部分数据发送给各个访问器Visitor

因为我们之前介绍 JVM8 的字节码文件格式,因此我们选择 ASM 也是适配 JVM8,其实就是openjdk8 中源码。

如果对字节码中结构和信息不清楚的,请先阅读这些文章:字节码文件(ClassFile)详解,字节码的属性,指令集

二. 访问器Visitor

2.1 类访问器ClassVisitor

2.1.1 ClassVisitor

public abstract class ClassVisitor {
    protected final int api;
    protected ClassVisitor cv;
    public ClassVisitor(final int api) {
        this(api, null);
    }
    public ClassVisitor(final int api, final ClassVisitor cv) {
        if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
            throw new IllegalArgumentException();
        }
        this.api = api;
        this.cv = cv;
    }
    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        if (cv != null) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
    }
    public void visitSource(String source, String debug) {
        if (cv != null) {
            cv.visitSource(source, debug);
        }
    }
    public void visitOuterClass(String owner, String name, String desc) {
        if (cv != null) {
            cv.visitOuterClass(owner, name, desc);
        }
    }
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (cv != null) {
            return cv.visitAnnotation(desc, visible);
        }
        return null;
    }
    public AnnotationVisitor visitTypeAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (cv != null) {
            return cv.visitTypeAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }
    public void visitAttribute(Attribute attr) {
        if (cv != null) {
            cv.visitAttribute(attr);
        }
    }
    public void visitInnerClass(String name, String outerName,
            String innerName, int access) {
        if (cv != null) {
            cv.visitInnerClass(name, outerName, innerName, access);
        }
    }
    public FieldVisitor visitField(int access, String name, String desc,
            String signature, Object value) {
        if (cv != null) {
            return cv.visitField(access, name, desc, signature, value);
        }
        return null;
    }
    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        if (cv != null) {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
        return null;
    }
    public void visitEnd() {
        if (cv != null) {
            cv.visitEnd();
        }
    }
}

可以看到ClassVisitor 中又有一个 ClassVisitor cv 成员变量,ClassVisitor 中方法实现都是调用 ClassVisitor cv 这个成员变量对应方法。

2.1.2 ClassFile 结构

首先让我们回忆一下,ClassFile 文件结构:

ClassFile {
   u4 magic;
   u2 minor_version;
   u2 major_version;
   u2 constant_pool_count;
   cp_info constant_pool[constant_pool_count-1];
   u2 access_flags;
   u2 this_class;
   u2 super_class;
   u2 interfaces_count;
   u2 interfaces[interfaces_count];
   u2 fields_count;
   field_info fields[fields_count];
   u2 methods_count;
   method_info methods[methods_count];
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

分为魔数,版本,常量池,访问标志,类名,继承的父类名,实现的接口集合,字段列表,方法列表和ClassFile的属性列表。

2.1.3 ClassVisitor 中的方法

方法的调用顺序如下:

 visit 
[ visitSource ] 
[ visitOuterClass ] 
( visitAnnotation | visitTypeAnnotation | visitAttribute )* 
( visitInnerClass | visitField | visitMethod )* 
visitEnd.
  1. void visit(int version, int access, String name, String signature,String superName, String[] interfaces)

    字节码文件的版本version,访问标志access,类名name,类泛型签名属性signature,继承的父类名 superName 和 实现的接口集合interfaces

  2. void visitSource(String source, String debug)

    字节码文件中SourceFileSourceDebugExtension 这两个属性信息。

  3. void visitOuterClass(String owner, String name, String desc)

    字节码文件中 EnclosingMethod 属性信息。

  4. AnnotationVisitor visitAnnotation(String desc, boolean visible)
    • 字节码文件中 RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations属性信息。
    • desc 表示注解类名的描述符,visible 表示这个注解可不可见。
  5. `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
    • 字节码文件中 RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations属性信息。
    • desc 表示注解类名的描述符,visible 表示这个注解可不可见。
  6. void visitAttribute(Attribute attr)

    这个是字节码文件中自定义的属性,不是当前 JVM 规定的属性值。

  7. void visitInnerClass(String name, String outerName, String innerName, int access)

    字节码文件中 InnerClasses 属性信息。

  8. FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
    • 字节码文件中字段列表。
    • 这里包含了字段的访问标志access,字段名name,字段描述符 desc, 字段签名信息signature和字段常量属性ConstantValue的值 value
  9. MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
    • 字节码文件中方法列表。
    • 这里包含了方法的访问标志access,方法名name,方法描述符 desc, 方法签名信息signature 和方法抛出异常列表exceptions
  10. void visitEnd()

表示字节码文件访问结束。

2.2 注解访问器AnnotationVisitor

AnnotationVisitor主要是用来接收注解相关属性的,主要就是 RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault,RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations这七个属性了。

注解的类名信息已经在 AnnotationVisitor visitAnnotation(String desc, boolean visible)AnnotationVisitor visitTypeAnnotation(...) 中获取了。
这个 AnnotationVisitor 只会获取注解的键值对信息。

2.2.1 AnnotationVisitor

public abstract class AnnotationVisitor {
    protected final int api;
    protected AnnotationVisitor av;
    public AnnotationVisitor(final int api) {
        this(api, null);
    }
    public AnnotationVisitor(final int api, final AnnotationVisitor av) {
        if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
            throw new IllegalArgumentException();
        }
        this.api = api;
        this.av = av;
    }
    public void visit(String name, Object value) {
        if (av != null) {
            av.visit(name, value);
        }
    }
    public void visitEnum(String name, String desc, String value) {
        if (av != null) {
            av.visitEnum(name, desc, value);
        }
    }
    public AnnotationVisitor visitAnnotation(String name, String desc) {
        if (av != null) {
            return av.visitAnnotation(name, desc);
        }
        return null;
    }
    public AnnotationVisitor visitArray(String name) {
        if (av != null) {
            return av.visitArray(name);
        }
        return null;
    }
    public void visitEnd() {
        if (av != null) {
            av.visitEnd();
        }
    }
}

ClassVisitor 一样,也有一个 AnnotationVisitor av 成员变量,来实现AnnotationVisitor 方法。

2.2.2 annotation 结构

还记得注解的数据结构么:

annotation {
   u2 type_index;
   u2 num_element_value_pairs;
   {   u2   element_name_index;
       element_value value;
   } element_value_pairs[num_element_value_pairs];
}

element_value {
     u1 tag;
     union {
         u2 const_value_index;
         { 
            u2 type_name_index;
            u2 const_name_index;
         } enum_const_value;
         u2 class_info_index;
         annotation annotation_value;
         {  
             u2 num_values;
             element_value values[num_values];
         } array_value;
     } value;
}
  • 其中 type_index 表示注解描述符常量池索引。
  • element_value_pairs[num_element_value_pairs] 表示注解键值对信息。
  • 注解键值分为五种类型,常量const_value_index,枚举enum_const_value,类class_info_index,注解annotation_value 和数组类型array_value

2.2.3 AnnotationVisitor 中的方法

方法调用顺序如下:

( visit | visitEnum | visitAnnotation | visitArray )* 
visitEnd
  1. void visit(String name, Object value)
    • 包括注解中常量类型键值对信息。
    • 还包括注解中类class_info_index类型键值对信息。
  2. void visitEnum(String name, String desc, String value)

    注解中枚举类型键值对信息。

  3. AnnotationVisitor visitAnnotation(String name, String desc)

    注解中注解类型键值对信息,返回这个注解类型键值的注解访问器 AnnotationVisitor

  4. AnnotationVisitor visitArray(String name)

    注解中数组类型键值对信息,也返回一个 AnnotationVisitor
    其实数组类型和注解类型的区别,注解类型有多个键值对集合,而数组类型只有键值的列表,没有键name

  5. void visitEnd()

    表示注解访问结束。

2.3 字段访问器 FieldVisitor

  • 字段的访问标志access,字段名name,字段描述符 desc, 字段签名信息signature和字段常量属性ConstantValue的值 value 已经在 visitField(...) 方法中获取了。
  • FieldVisitor 只有字段其他属性的获取。

2.3.1 FieldVisitor

public abstract class FieldVisitor {
    protected final int api;
    protected FieldVisitor fv;
    public FieldVisitor(final int api) {
        this(api, null);
    }
    public FieldVisitor(final int api, final FieldVisitor fv) {
        if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
            throw new IllegalArgumentException();
        }
        this.api = api;
        this.fv = fv;
    }
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (fv != null) {
            return fv.visitAnnotation(desc, visible);
        }
        return null;
    }
    public AnnotationVisitor visitTypeAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (fv != null) {
            return fv.visitTypeAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }
    public void visitAttribute(Attribute attr) {
        if (fv != null) {
            fv.visitAttribute(attr);
        }
    }
    public void visitEnd() {
        if (fv != null) {
            fv.visitEnd();
        }
    }
}

2.3.2 field_info 结构

field_info {
   u2 access_flags;
   u2 name_index;
   u2 descriptor_index;
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

字段中属性有:

  • ConstantValue : 通过 final 关键字修饰的字段对应的常量值。
  • Synthetic : 标志字段,方法和类由编译器自动生成。
  • Deprecated : 声明字段,方法和类将弃用。
  • Signature : 记录字段,方法和类的泛型信息。
  • RuntimeVisibleAnnotations : 可见的字段,方法和类注解。
  • RuntimeInvisibleAnnotations : 不可见的字段,方法和类注解。
  • RuntimeVisibleTypeAnnotations : 可见的类型注解,主要用于实现 JSR 308
  • RuntimeInvisibleTypeAnnotations : 不可见的类型注解,主要用于实现 JSR 308

2.3.3 FieldVisitor 中的方法

方法调用顺序如下:

( visitAnnotation | visitTypeAnnotation | visitAttribute )*
visitEnd
  1. AnnotationVisitor visitAnnotation(String desc, boolean visible)
    • 字节码文件中 RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations属性信息。
    • desc 表示注解类名的描述符,visible 表示这个注解可不可见。
  2. `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
    • 字节码文件中 RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations属性信息。
    • desc 表示注解类名的描述符,visible 表示这个注解可不可见。
  3. void visitAttribute(Attribute attr)

    这个是字节码文件中自定义的属性,不是当前 JVM 规定的属性值。

  4. void visitEnd()

    表示字段访问结束。

2.4 方法访问器 MethodVisitor

2.4.1 MethodVisitor

public abstract class MethodVisitor {
    protected final int api;
    protected MethodVisitor mv;
    public MethodVisitor(final int api) {
        this(api, null);
    }
    public MethodVisitor(final int api, final MethodVisitor mv) {
        if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
            throw new IllegalArgumentException();
        }
        this.api = api;
        this.mv = mv;
    }
    public void visitParameter(String name, int access) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            mv.visitParameter(name, access);
        }
    }
    public AnnotationVisitor visitAnnotationDefault() {
        if (mv != null) {
            return mv.visitAnnotationDefault();
        }
        return null;
    }
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (mv != null) {
            return mv.visitAnnotation(desc, visible);
        }
        return null;
    }
    public AnnotationVisitor visitTypeAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitTypeAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }
    public AnnotationVisitor visitParameterAnnotation(int parameter,
            String desc, boolean visible) {
        if (mv != null) {
            return mv.visitParameterAnnotation(parameter, desc, visible);
        }
        return null;
    }
    public void visitAttribute(Attribute attr) {
        if (mv != null) {
            mv.visitAttribute(attr);
        }
    }
    public void visitCode() {
        if (mv != null) {
            mv.visitCode();
        }
    }
    public void visitFrame(int type, int nLocal, Object[] local, int nStack,
            Object[] stack) {
        if (mv != null) {
            mv.visitFrame(type, nLocal, local, nStack, stack);
        }
    }
    public void visitInsn(int opcode) {
        if (mv != null) {
            mv.visitInsn(opcode);
        }
    }
    public void visitIntInsn(int opcode, int operand) {
        if (mv != null) {
            mv.visitIntInsn(opcode, operand);
        }
    }
    public void visitVarInsn(int opcode, int var) {
        if (mv != null) {
            mv.visitVarInsn(opcode, var);
        }
    }
    public void visitTypeInsn(int opcode, String type) {
        if (mv != null) {
            mv.visitTypeInsn(opcode, type);
        }
    }
    public void visitFieldInsn(int opcode, String owner, String name,
            String desc) {
        if (mv != null) {
            mv.visitFieldInsn(opcode, owner, name, desc);
        }
    }
    @Deprecated
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc) {
        if (api >= Opcodes.ASM5) {
            boolean itf = opcode == Opcodes.INVOKEINTERFACE;
            visitMethodInsn(opcode, owner, name, desc, itf);
            return;
        }
        if (mv != null) {
            mv.visitMethodInsn(opcode, owner, name, desc);
        }
    }
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc, boolean itf) {
        if (api < Opcodes.ASM5) {
            if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
                throw new IllegalArgumentException(
                        "INVOKESPECIALTATIC on interfaces require ASM 5");
            }
            visitMethodInsn(opcode, owner, name, desc);
            return;
        }
        if (mv != null) {
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
        }
    }
    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
            Object... bsmArgs) {
        if (mv != null) {
            mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
        }
    }
    public void visitJumpInsn(int opcode, Label label) {
        if (mv != null) {
            mv.visitJumpInsn(opcode, label);
        }
    }
    public void visitLabel(Label label) {
        if (mv != null) {
            mv.visitLabel(label);
        }
    }
    public void visitLdcInsn(Object cst) {
        if (mv != null) {
            mv.visitLdcInsn(cst);
        }
    }
    public void visitIincInsn(int var, int increment) {
        if (mv != null) {
            mv.visitIincInsn(var, increment);
        }
    }
    public void visitTableSwitchInsn(int min, int max, Label dflt,
            Label... labels) {
        if (mv != null) {
            mv.visitTableSwitchInsn(min, max, dflt, labels);
        }
    }
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        if (mv != null) {
            mv.visitLookupSwitchInsn(dflt, keys, labels);
        }
    }
    public void visitMultiANewArrayInsn(String desc, int dims) {
        if (mv != null) {
            mv.visitMultiANewArrayInsn(desc, dims);
        }
    }
    public AnnotationVisitor visitInsnAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitInsnAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }
    public void visitTryCatchBlock(Label start, Label end, Label handler,
            String type) {
        if (mv != null) {
            mv.visitTryCatchBlock(start, end, handler, type);
        }
    }
    public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }
    public void visitLocalVariable(String name, String desc, String signature,
            Label start, Label end, int index) {
        if (mv != null) {
            mv.visitLocalVariable(name, desc, signature, start, end, index);
        }
    }
    public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
            TypePath typePath, Label[] start, Label[] end, int[] index,
            String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitLocalVariableAnnotation(typeRef, typePath, start,
                    end, index, desc, visible);
        }
        return null;
    }
    public void visitLineNumber(int line, Label start) {
        if (mv != null) {
            mv.visitLineNumber(line, start);
        }
    }
    public void visitMaxs(int maxStack, int maxLocals) {
        if (mv != null) {
            mv.visitMaxs(maxStack, maxLocals);
        }
    }
    public void visitEnd() {
        if (mv != null) {
            mv.visitEnd();
        }
    }
}

2.4.2 method_info 结构

method_info {
   u2 access_flags;
   u2 name_index;
   u2 descriptor_index;
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

方法中的属性有:

  • Code : 方法对应字节码指令。
  • Exceptions : 方法抛出的异常列表。
  • RuntimeVisibleParameterAnnotations : 可见的方法参数注解。
  • RuntimeInvisibleParameterAnnotations : 不可见的方法参数注解。
  • AnnotationDefault : 记录注解类元素默认值。
  • MethodParameters : 将方法参数参数名编译到字节码文件中(编译时加上 -parameters 参数)。
  • Synthetic : 标志字段,方法和类由编译器自动生成。
  • Deprecated : 声明字段,方法和类将弃用。
  • Signature : 记录字段,方法和类的泛型信息。
  • RuntimeVisibleAnnotations : 可见的字段,方法和类注解。
  • RuntimeInvisibleAnnotations : 不可见的字段,方法和类注解。
  • RuntimeVisibleTypeAnnotations : 可见的类型注解,主要用于实现 JSR 308
  • RuntimeInvisibleTypeAnnotations : 不可见的类型注解,主要用于实现 JSR 308

2.4.3 MethodVisitor 中的方法

方法调用顺序如下

( visitParameter )*
[ visitAnnotationDefault ] 
( visitAnnotation | visitTypeAnnotation | visitAttribute )* 
[ visitCode ( visitFrame | visitXInsn | visitLabel | visitInsnAnnotation | visitTryCatchBlock | visitTryCatchBlockAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* visitMaxs ]
visitEnd

可以看出 MethodVisitor 中的方法比较复杂,主要是因为 Code 属性数据,需要生成方法指令集。

  1. void visitParameter(String name, int access)

    方法中MethodParameters 属性,获取方法参数的参数名name和参数访问标志access

  2. AnnotationVisitor visitAnnotationDefault()

    方法中AnnotationDefault 属性,只会出现在注解类中,注解类方法默认返回值信息。

  3. AnnotationVisitor visitAnnotation(String desc, boolean visible)

    方法中RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations 属性

  4. AnnotationVisitor visitTypeAnnotation(int typeRef,TypePath typePath, String desc, boolean visible)

    方法中RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations 属性

  5. AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible)

    方法中RuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotations 属性。

  6. void visitAttribute(Attribute attr)

    方法中自定义属性。

  7. void visitEnd() :

    方法访问结束。

2.4.3 Code 属性相关方法

我们知道方法的操作其实就是对应一个一个的JVM 指令,而这些指令就存在 Code 属性中。

2.4.3.1 Code 属性结构

Code_attribute {
   u2 attribute_name_index;
   u4 attribute_length;
   u2 max_stack;
   u2 max_locals;
   u4 code_length;
   u1 code[code_length];
   u2 exception_table_length;
   {   u2 start_pc;
       u2 end_pc;
       u2 handler_pc;
       u2 catch_type;
   }   exception_table[exception_table_length];
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

Code 中包含的属性有:

  • LineNumberTable : java 源码中方法中字节指令和行号对应关系。
  • LocalVariableTable : 方法中局部变量描述。
  • LocalVariableTypeTable : 方法中泛型局部变量描述。
  • StackMapTable : 记录数据供类型检查验证器检查,来验证方法局部变量表和操作数栈所需类型是否匹配。
  • RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations

2.4.3.2 Code 属性对应方法

方法流程如下:

visitCode
( visitFrame | visitXInsn | visitLabel | visitInsnAnnotation | visitTryCatchBlock | visitTryCatchBlockAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )* 
visitMaxs 
  1. void visitCode() : 访问Code 属性开始。
  2. void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) : 访问 StackMapTable 属性。

    关于 StackMapTable 详细说明请看字节码的属性

  3. void visitInsn(int opcode) : 访问零操作数指令

    这些指令有 NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.

  4. void visitIntInsn(int opcode, int operand) : 访问操作数就是整型值的指令。
    • 也就是说这个操作数不代表索引这些,比如 BIPUSH 指令,就是将一个byte 类型数放入操作数栈。
    • 这些指令有BIPUSH, SIPUSHNEWARRAY
  5. void visitVarInsn(int opcode, int var) : 访问局部变量指令

    这些指令包括 ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET

  6. void visitTypeInsn(int opcode, String type) : 访问类型指令。

    这些指令包括 NEW, ANEWARRAY, CHECKCAST or INSTANCEOF

  7. void visitFieldInsn(int opcode, String owner, String name, String desc) : 访问字段指令。
    • 字段所属的类名owner,字段名name和字段描述符desc
    • 这些指令包括 GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD
  8. void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) : 访问方法指令。
    • 方法所属的类名owner,方法名name,方法描述符desc和是否为接口方法itf
    • 这些指令包括 INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE
  9. visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) : 访问动态方法指令。
    • 动态方法名name,动态方法描述desc,引导方法处理器bsm 和引导方法处理器参数bsmArgs
    • 这个指令就是 invokedynamic
  10. visitJumpInsn(int opcode, Label label) : 访问程序跳转指令。

    这些指令包括 IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL

  11. void visitLabel(Label label) : 主要是通过 label 记录当前 Code 指令的地址。
  12. void visitLdcInsn(Object cst) : 访问LDC指令。
  13. void visitIincInsn(int var, int increment) : 访问IINC指令。
  14. void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) : 访问TABLESWITCH指令。
  15. void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) : 访问LOOKUPSWITCH指令。
  16. void visitMultiANewArrayInsn(String desc, int dims) : 访问MULTIANEWARRAY指令。
  17. void visitTryCatchBlock(Label start, Label end, Label handler, String type) : 访问try catch 代码块,即Code 属性中exception_table 中的数据。
  18. AnnotationVisitor visitInsnAnnotation(int typeRef,TypePath typePath, String desc, boolean visible) : 访问RuntimeVisibleTypeAnnotations 属性值。
  19. void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) : 访问 LocalVariableTableLocalVariableTypeTable 属性值。
  20. void visitLineNumber(int line, Label start) : 访问 LineNumberTable 属性值。
  21. void visitMaxs(int maxStack, int maxLocals) : 访问Code属性中max_stackmax_locals 值。

三. 类读取器 ClassReader

ClassReader 可以读取字节码文件,并分发给各个访问器 Visitor

3.1 初始化方法

public ClassReader(final InputStream is) throws IOException {
        this(readClass(is, false));
    }
 public ClassReader(final String name) throws IOException {
        this(readClass(
                ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
                        + ".class"), true));
    }
public ClassReader(final byte[] b) {
        this(b, 0, b.length);
    }

    public ClassReader(final byte[] b, final int off, final int len) {
        this.b = b;
        // checks the class version
        // 字节码版本超过 1.8,直接报错
        if (readShort(off + 6) > Opcodes.V1_8) {
            throw new IllegalArgumentException();
        }
        // parses the constant pool
        // 解析常量池
        items = new int[readUnsignedShort(off + 8)];
        int n = items.length;
        strings = new String[n];
        int max = 0;
        // 10个字节包括  u4 magic,u2 minor_version,u2 major_version 和 u2 constant_pool_count
        int index = off + 10;
        for (int i = 1; i < n; ++i) {
            // +1 是为了去除常量池类型  u1 tag
            // items 中保存常量数据开始位置
            items[i] = index + 1;
            int size;
            switch (b[index]) {
            case ClassWriter.FIELD:
            case ClassWriter.METH:
            case ClassWriter.IMETH:
            case ClassWriter.INT:
            case ClassWriter.FLOAT:
            case ClassWriter.NAME_TYPE:
            case ClassWriter.INDY:
                size = 5;
                break;
            case ClassWriter.LONG:
            case ClassWriter.DOUBLE:
                size = 9;
                ++i;
                break;
            case ClassWriter.UTF8:
                size = 3 + readUnsignedShort(index + 1);
                if (size > max) {
                    max = size;
                }
                break;
            case ClassWriter.HANDLE:
                size = 4;
                break;
            // case ClassWriter.CLASS:
            // case ClassWriter.STR:
            // case ClassWriter.MTYPE
            default:
                size = 3;
                break;
            }
            index += size;
        }
        // 记录最大 UTF8 类型常量的字节数。
        maxStringLength = max;
        // the class header information starts just after the constant pool
        // header 就是 access_flags 开始位置
        header = index;
    }

主要就是获取字节码文件的字节数组byte[] b, 然后解析字节码的常量池,得到三个成员变量:

  • items : 记录常量池中所有常量数据在字节数组b 的位置,方便常量数据。
  • strings : 用来缓存 UTF8 类型常量的字符串。
  • header : 字节码中 access_flags 开始位置。

3.2 accept 方法

accept 方法就是接收一个 ClassVisitor 对象,将字节码文件中的数据发送到 类访问器ClassVisitor
接下来我们一步一步分析:

    public void accept(final ClassVisitor classVisitor,
            final Attribute[] attrs, final int flags) {
        int u = header; // current offset in the class file
        char[] c = new char[maxStringLength]; // buffer used to read strings

        Context context = new Context();
        context.attrs = attrs;
        context.flags = flags;
        context.buffer = c;

        // reads the class declaration
        int access = readUnsignedShort(u);
        String name = readClass(u + 2, c);
        String superClass = readClass(u + 4, c);
        String[] interfaces = new String[readUnsignedShort(u + 6)];
        u += 8;
        for (int i = 0; i < interfaces.length; ++i) {
            interfaces[i] = readClass(u, c);
            u += 2;
        }
        .......
}

首先读取了字节码文件中的访问标志 access,类名name,父类名superClass和接口列表interfaces

   public void accept(final ClassVisitor classVisitor,
            final Attribute[] attrs, final int flags) {
         ......
       Attribute attributes = null;
        u = getAttributes();
        for (int i = readUnsignedShort(u); i > 0; --i) {
            String attrName = readUTF8(u + 2, c);
            // tests are sorted in decreasing frequency order
            // (based on frequencies observed on typical classes)
            if ("SourceFile".equals(attrName)) {
                sourceFile = readUTF8(u + 8, c);
            } else if ("InnerClasses".equals(attrName)) {
                innerClasses = u + 8;
            } 
         ......
}
  • 先通过getAttributes() 方法获取类属性列表的开始位置。
  • 然后获取属性个数,通过遍历获取所有类属性值。
   public void accept(final ClassVisitor classVisitor,
            final Attribute[] attrs, final int flags) {
         ......
        // visits the class declaration
        classVisitor.visit(readInt(items[1] - 7), access, name, signature,
                superClass, interfaces);

        // visits the source and debug info
        if ((flags & SKIP_DEBUG) == 0
                && (sourceFile != null || sourceDebug != null)) {
            classVisitor.visitSource(sourceFile, sourceDebug);
        }

        // visits the outer class
        if (enclosingOwner != null) {
            classVisitor.visitOuterClass(enclosingOwner, enclosingName,
                    enclosingDesc);
        }

        // visits the class annotations and type annotations
        if (ANNOTATIONS && anns != 0) {
            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitAnnotation(readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && ianns != 0) {
            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitAnnotation(readUTF8(v, c), false));
            }
        }
        if (ANNOTATIONS && tanns != 0) {
            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && itanns != 0) {
            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        classVisitor.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), false));
            }
        }

        // visits the attributes
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            classVisitor.visitAttribute(attributes);
            attributes = attr;
        }

        // visits the inner classes
        if (innerClasses != 0) {
            int v = innerClasses + 2;
            for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
                classVisitor.visitInnerClass(readClass(v, c),
                        readClass(v + 2, c), readUTF8(v + 4, c),
                        readUnsignedShort(v + 6));
                v += 8;
            }
        }

        // visits the fields and methods
        u = header + 10 + 2 * interfaces.length;
        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
            u = readField(classVisitor, context, u);
        }
        u += 2;
        for (int i = readUnsignedShort(u - 2); i > 0; --i) {
            u = readMethod(classVisitor, context, u);
        }

        // visits the end of the class
        classVisitor.visitEnd();
}

这里就可以看到 ClassVisitor 中方法访问顺序了。

3.3 readAnnotationValues 方法

private int readAnnotationValues(int v, final char[] buf,
            final boolean named, final AnnotationVisitor av) {
        // 获取注解键值对个数,或者键值的个数,如果 named 为false
        int i = readUnsignedShort(v);
        v += 2;
        if (named) {
            for (; i > 0; --i) {
                v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
            }
        } else {
            for (; i > 0; --i) {
                // 肯定是 array 类型,没有键'name'
                v = readAnnotationValue(v, buf, null, av);
            }
        }
        if (av != null) {
            av.visitEnd();
        }
        return v;
    }

这里有一点不好,应该增加 readAnnotationElementValue 方法,这样就可以区分 { u2 element_name_index; element_value value;}element_value value 区别,就不需要通过 named 进行区别了。

    private int readAnnotationValue(int v, final char[] buf, final String name,
            final AnnotationVisitor av) {
        int i;
        if (av == null) {
            switch (b[v] & 0xFF) {
            case 'e': // enum_const_value
                return v + 5;
            case '@': // annotation_value
                return readAnnotationValues(v + 3, buf, true, null);
            case '[': // array_value
                return readAnnotationValues(v + 1, buf, false, null);
            default:
                return v + 3;
            }
        }
        switch (b[v++] & 0xFF) {
        case 'I': // pointer to CONSTANT_Integer
        case 'J': // pointer to CONSTANT_Long
        case 'F': // pointer to CONSTANT_Float
        case 'D': // pointer to CONSTANT_Double
            av.visit(name, readConst(readUnsignedShort(v), buf));
            v += 2;
            break;
        case 'B': // pointer to CONSTANT_Byte
            av.visit(name,
                    (byte) readInt(items[readUnsignedShort(v)]));
            v += 2;
            break;
        case 'Z': // pointer to CONSTANT_Boolean
            av.visit(name,
                    readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
                            : Boolean.TRUE);
            v += 2;
            break;
        case 'S': // pointer to CONSTANT_Short
            av.visit(name,
                    (short) readInt(items[readUnsignedShort(v)]));
            v += 2;
            break;
        case 'C': // pointer to CONSTANT_Char
            av.visit(name,
                    (char) readInt(items[readUnsignedShort(v)]));
            v += 2;
            break;
        case 's': // pointer to CONSTANT_Utf8
            av.visit(name, readUTF8(v, buf));
            v += 2;
            break;
        case 'e': // enum_const_value
            av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
            v += 4;
            break;
        case 'c': // class_info
            av.visit(name, Type.getType(readUTF8(v, buf)));
            v += 2;
            break;
        case '@': // annotation_value
            v = readAnnotationValues(v + 2, buf, true,
                    av.visitAnnotation(name, readUTF8(v, buf)));
            break;
        case '[': // array_value
            int size = readUnsignedShort(v);
            v += 2;
            if (size == 0) {
                return readAnnotationValues(v - 2, buf, false,
                        av.visitArray(name));
            }
            switch (this.b[v++] & 0xFF) {
            case 'B':
                byte[] bv = new byte[size];
                for (i = 0; i < size; i++) {
                    bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
                    v += 3;
                }
                av.visit(name, bv);
                --v;
                break;
            case 'Z':
                boolean[] zv = new boolean[size];
                for (i = 0; i < size; i++) {
                    zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
                    v += 3;
                }
                av.visit(name, zv);
                --v;
                break;
            case 'S':
                short[] sv = new short[size];
                for (i = 0; i < size; i++) {
                    sv[i] = (short) readInt(items[readUnsignedShort(v)]);
                    v += 3;
                }
                av.visit(name, sv);
                --v;
                break;
            case 'C':
                char[] cv = new char[size];
                for (i = 0; i < size; i++) {
                    cv[i] = (char) readInt(items[readUnsignedShort(v)]);
                    v += 3;
                }
                av.visit(name, cv);
                --v;
                break;
            case 'I':
                int[] iv = new int[size];
                for (i = 0; i < size; i++) {
                    iv[i] = readInt(items[readUnsignedShort(v)]);
                    v += 3;
                }
                av.visit(name, iv);
                --v;
                break;
            case 'J':
                long[] lv = new long[size];
                for (i = 0; i < size; i++) {
                    lv[i] = readLong(items[readUnsignedShort(v)]);
                    v += 3;
                }
                av.visit(name, lv);
                --v;
                break;
            case 'F':
                float[] fv = new float[size];
                for (i = 0; i < size; i++) {
                    fv[i] = Float
                            .intBitsToFloat(readInt(items[readUnsignedShort(v)]));
                    v += 3;
                }
                av.visit(name, fv);
                --v;
                break;
            case 'D':
                double[] dv = new double[size];
                for (i = 0; i < size; i++) {
                    dv[i] = Double
                            .longBitsToDouble(readLong(items[readUnsignedShort(v)]));
                    v += 3;
                }
                av.visit(name, dv);
                --v;
                break;
            default:
                v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
            }
        }
        return v;
    }

读取注解不同类型键值对的数据,值得注意的是读取 array 类型时,它选择在当前方法中直接通过循环处理,而不是使用递归。

3.4 readField 方法

   private int readField(final ClassVisitor classVisitor,
            final Context context, int u) {
        // reads the field declaration
        char[] c = context.buffer;
        int access = readUnsignedShort(u);
        String name = readUTF8(u + 2, c);
        String desc = readUTF8(u + 4, c);
        u += 6;
        ......
}

先读取字段的访问标志access,字段名name和字段描述符desc

   private int readField(final ClassVisitor classVisitor,
            final Context context, int u) {
         ......
   for (int i = readUnsignedShort(u); i > 0; --i) {
            String attrName = readUTF8(u + 2, c);
            // tests are sorted in decreasing frequency order
            // (based on frequencies observed on typical classes)
            if ("ConstantValue".equals(attrName)) {
                int item = readUnsignedShort(u + 8);
                value = item == 0 ? null : readConst(item, c);
            } else if (SIGNATURES && "Signature".equals(attrName)) {
                signature = readUTF8(u + 8, c);
            } else if ("Deprecated".equals(attrName)) {
                access |= Opcodes.ACC_DEPRECATED;
            } else if ("Synthetic".equals(attrName)) {
                access |= Opcodes.ACC_SYNTHETIC
                        | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
            }
            ......
         }
     ......
}

然后读取字段的属性值。

   private int readField(final ClassVisitor classVisitor,
            final Context context, int u) {
         ......
        FieldVisitor fv = classVisitor.visitField(access, name, desc,
                signature, value);
        if (fv == null) {
            return u;
        }

        // visits the field annotations and type annotations
        if (ANNOTATIONS && anns != 0) {
            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        fv.visitAnnotation(readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && ianns != 0) {
            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        fv.visitAnnotation(readUTF8(v, c), false));
            }
        }
        if (ANNOTATIONS && tanns != 0) {
            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        fv.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && itanns != 0) {
            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        fv.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), false));
            }
        }

        // visits the field attributes
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            fv.visitAttribute(attributes);
            attributes = attr;
        }

        // visits the end of the field
        fv.visitEnd();

        return u;
}

这个就是 FieldVisitor 中方法调用顺序。

3.5 readMethod 方法

    private int readMethod(final ClassVisitor classVisitor,
            final Context context, int u) {
        // reads the method declaration
        char[] c = context.buffer;
        context.access = readUnsignedShort(u);
        context.name = readUTF8(u + 2, c);
        context.desc = readUTF8(u + 4, c);
        u += 6;
        .......
   }

先读取方法的访问标志access,方法名name和方法描述符desc

    private int readMethod(final ClassVisitor classVisitor,
            final Context context, int u) {
        .......
      for (int i = readUnsignedShort(u); i > 0; --i) {
            String attrName = readUTF8(u + 2, c);
            // tests are sorted in decreasing frequency order
            // (based on frequencies observed on typical classes)
            if ("Code".equals(attrName)) {
                if ((context.flags & SKIP_CODE) == 0) {
                    code = u + 8;
                }
            } else if ("Exceptions".equals(attrName)) {
                exceptions = new String[readUnsignedShort(u + 8)];
                exception = u + 10;
                for (int j = 0; j < exceptions.length; ++j) {
                    exceptions[j] = readClass(exception, c);
                    exception += 2;
                }
            } else if (SIGNATURES && "Signature".equals(attrName)) {
                signature = readUTF8(u + 8, c);
            } else if ("Deprecated".equals(attrName)) {
                context.access |= Opcodes.ACC_DEPRECATED;
            } 
           ......
        }
      .......
 }

然后读取方法的属性值。

    private int readMethod(final ClassVisitor classVisitor,
            final Context context, int u) {
        .......
       // visits the method declaration
        MethodVisitor mv = classVisitor.visitMethod(context.access,
                context.name, context.desc, signature, exceptions);
        if (mv == null) {
            return u;
        }

       if (WRITER && mv instanceof MethodWriter) {
            MethodWriter mw = (MethodWriter) mv;
            if (mw.cw.cr == this && signature == mw.signature) {
                boolean sameExceptions = false;
                if (exceptions == null) {
                    sameExceptions = mw.exceptionCount == 0;
                } else if (exceptions.length == mw.exceptionCount) {
                    sameExceptions = true;
                    for (int j = exceptions.length - 1; j >= 0; --j) {
                        exception -= 2;
                        if (mw.exceptions[j] != readUnsignedShort(exception)) {
                            sameExceptions = false;
                            break;
                        }
                    }
                }
                if (sameExceptions) {
                    /*
                     * we do not copy directly the code into MethodWriter to
                     * save a byte array copy operation. The real copy will be
                     * done in ClassWriter.toByteArray().
                     */
                    mw.classReaderOffset = firstAttribute;
                    mw.classReaderLength = u - firstAttribute;
                    return u;
                }
            }
        }

        // visit the method parameters
        if (methodParameters != 0) {
            for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
                mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
            }
        }

        // visits the method annotations
        if (ANNOTATIONS && dann != 0) {
            AnnotationVisitor dv = mv.visitAnnotationDefault();
            readAnnotationValue(dann, c, null, dv);
            if (dv != null) {
                dv.visitEnd();
            }
        }
        if (ANNOTATIONS && anns != 0) {
            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        mv.visitAnnotation(readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && ianns != 0) {
            for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        mv.visitAnnotation(readUTF8(v, c), false));
            }
        }
        if (ANNOTATIONS && tanns != 0) {
            for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        mv.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), true));
            }
        }
        if (ANNOTATIONS && itanns != 0) {
            for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
                v = readAnnotationTarget(context, v);
                v = readAnnotationValues(v + 2, c, true,
                        mv.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), false));
            }
        }
        if (ANNOTATIONS && mpanns != 0) {
            readParameterAnnotations(mv, context, mpanns, true);
        }
        if (ANNOTATIONS && impanns != 0) {
            readParameterAnnotations(mv, context, impanns, false);
        }

        // visits the method attributes
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            mv.visitAttribute(attributes);
            attributes = attr;
        }

        // visits the method code
        if (code != 0) {
            mv.visitCode();
            readCode(mv, context, code);
        }

        // visits the end of the method
        mv.visitEnd();

        return u;
    }
  • 这就是MethodVisitor 中方法调用顺序,不过 Code 属性相关方法,还是通过 readCode(mv, context, code) 方法决定。
  • if (WRITER && mv instanceof MethodWriter) 这个判断主要是为了没有修改的方法可以直接复制,而不用重新计算。

3.6 readCode 方法

这个方法比较复杂,源码有500多行代码,我就简单介绍一下。

    private void readCode(final MethodVisitor mv, final Context context, int u) {
        byte[] b = this.b;
        char[] c = context.buffer;
        int maxStack = readUnsignedShort(u);
        int maxLocals = readUnsignedShort(u + 2);
        int codeLength = readInt(u + 4);
        u += 8;
        if (codeLength > b.length - u) {
            throw new IllegalArgumentException();
        }
       // reads the bytecode to find the labels
        int codeStart = u;
        int codeEnd = u + codeLength;
    ......
 }

获取 Code 属性的操作数栈最大深度maxStack, 局部变量表大小maxLocals,指令集的大小codeLength,指令开始位置codeStart 和指令结束位置codeEnd

    private void readCode(final MethodVisitor mv, final Context context, int u) {
    ......
    while (u < codeEnd) {
            int offset = u - codeStart;
            int opcode = b[u] & 0xFF;
            switch (ClassWriter.TYPE[opcode]) {
            case ClassWriter.NOARG_INSN:
            case ClassWriter.IMPLVAR_INSN:
                u += 1;
                break;
            case ClassWriter.LABEL_INSN:
                readLabel(offset + readShort(u + 1), labels);
                u += 3;
            .....
      }
    ......
  }

这个 while 循环为了记录程序跳转指令对应的指令地址(即Label)。

    private void readCode(final MethodVisitor mv, final Context context, int u) {
    ......
      for (int i = readUnsignedShort(u); i > 0; --i) {
            Label start = readLabel(readUnsignedShort(u + 2), labels);
            Label end = readLabel(readUnsignedShort(u + 4), labels);
            Label handler = readLabel(readUnsignedShort(u + 6), labels);
            String type = readUTF8(items[readUnsignedShort(u + 8)], c);
            mv.visitTryCatchBlock(start, end, handler, type);
            u += 8;
        }
    ......
  }

处理 try catch 的代码块,即异常处理,对应Code 属性中的 exception_table

    private void readCode(final MethodVisitor mv, final Context context, int u) {
    ......
 for (int i = readUnsignedShort(u); i > 0; --i) {
            String attrName = readUTF8(u + 2, c);
            if ("LocalVariableTable".equals(attrName)) {
                if ((context.flags & SKIP_DEBUG) == 0) {
                    varTable = u + 8;
                    for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
                        int label = readUnsignedShort(v + 10);
                        if (labels[label] == null) {
                            readLabel(label, labels).status |= Label.DEBUG;
                        }
                        label += readUnsignedShort(v + 12);
                        if (labels[label] == null) {
                            readLabel(label, labels).status |= Label.DEBUG;
                        }
                        v += 10;
                    }
                }
            } else if ("LocalVariableTypeTable".equals(attrName)) {
                varTypeTable = u + 8;
            } 
          ......
     }
    ......
  }

读取Code 中的属性列表。

    private void readCode(final MethodVisitor mv, final Context context, int u) {
    ......
    // visits the instructions
        u = codeStart;
        while (u < codeEnd) {
            int offset = u - codeStart;

            // visits the label and line number for this offset, if any
            Label l = labels[offset];
            if (l != null) {
                mv.visitLabel(l);
                if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
                    mv.visitLineNumber(l.line, l);
                }
            }
          ......
     }
    ......
  }

读取Code 中的指令集,并调用MethodVisitor中相关visitXInsn 方法。

四. 类写入器ClassWriter

ClassReader 只能读取已经编译好的字节码文件,但是如果我们想要生成一个字节码文件,那么就必须使用这个类写入器ClassWriter了。

创建ClassWriter 必须传递一个 flags 标志,这个 flags 可以选择三个值,分别是:

  • 0 : 表示不做特殊处理。
  • COMPUTE_MAXS : 动态计算方法的 Code 属性中的max_stackmax_locals。如果我们改变了 Code 属性中指令,那么就可能需要计算max_stackmax_locals值了,否则JVM会执行方法失败。
  • COMPUTE_FRAMES : 动态计算方法的StackMapTable,如果方法中有程序跳转指令,那么必须要有StackMapTable,否则JVM方法验证不通过,拒绝执行。

我们仔细想一下字节码文件中,最难处理的是那部分呢?

肯定就是常量池了,因为我们的常量,类引用,字段引用,方法引用等等,都必须先在常量池中定义,而用到的地方只需要使用对应常量池索引就可以了。

那么我们先看看ClassWriter如何处理常量池,用到一个常量项Item

4.1 常量项Item

final class Item {
    int index;
    int type;
    int intVal;
    long longVal;
    String strVal1;
    String strVal2;
    String strVal3;
    int hashCode;
    Item next;
}
  • index : 表示这个常量项在常量池中的索引。
  • type : 表示这个常量项的类型,目前JVM814 种常量类型。
  • intVal : 表示int 类型常量的值,包括 INTFLOAT
  • longVal : 表示long 类型常量的值,包括 LONGDOUBLE
  • strVal1,strVal2strVal3 : 记录字符串的值,有些类型可能有多个字符串值。
  • hashCode : 表示这个常量项的哈希值。
  • next : 下一个常量项,因为通过哈希表储存常量,这里 next 就是通过链地址法解决哈希冲突。

4.2 创建常量方法

通过一系列new 方法创建常量。

    Item newConstItem(final Object cst) {
        if (cst instanceof Integer) {
            int val = ((Integer) cst).intValue();
            return newInteger(val);
        } else if (cst instanceof Byte) {
            int val = ((Byte) cst).intValue();
            return newInteger(val);
        } else if (cst instanceof Character) {
            int val = ((Character) cst).charValue();
            return newInteger(val);
        } else if (cst instanceof Short) {
            int val = ((Short) cst).intValue();
            return newInteger(val);
        } else if (cst instanceof Boolean) {
            int val = ((Boolean) cst).booleanValue() ? 1 : 0;
            return newInteger(val);
        } else if (cst instanceof Float) {
            float val = ((Float) cst).floatValue();
            return newFloat(val);
        } else if (cst instanceof Long) {
            long val = ((Long) cst).longValue();
            return newLong(val);
        } else if (cst instanceof Double) {
            double val = ((Double) cst).doubleValue();
            return newDouble(val);
        } else if (cst instanceof String) {
            return newString((String) cst);
        } else if (cst instanceof Type) {
            Type t = (Type) cst;
            int s = t.getSort();
            if (s == Type.OBJECT) {
                return newClassItem(t.getInternalName());
            } else if (s == Type.METHOD) {
                return newMethodTypeItem(t.getDescriptor());
            } else { // s == primitive type or array
                return newClassItem(t.getDescriptor());
            }
        } else if (cst instanceof Handle) {
            Handle h = (Handle) cst;
            return newHandleItem(h.tag, h.owner, h.name, h.desc);
        } else {
            throw new IllegalArgumentException("value " + cst);
        }
    }

根据 cst 类型调用不同的 new 方法创建对应的常量项。

    private Item get(final Item key) {
        Item i = items[key.hashCode % items.length];
        while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
            i = i.next;
        }
        return i;
    }

    Item newInteger(final int value) {
        key.set(value);
        Item result = get(key);
        if (result == null) {
            pool.putByte(INT).putInt(value);
            result = new Item(index++, key);
            put(result);
        }
        return result;
    }
  • items 就是一个哈希表,存储着所有常量。
  • get(final Item key) 方法尝试从哈希表items 中获取 key 对应的常量。
  • 如果哈希表中没有这个常量项,那么就创建。先将常量存到常量池pool 中,然后创建对应常量项Item,并存到哈希表items中。

4.3 访问器方法

4.3.1 visit(...)

    public final void visit(final int version, final int access,
            final String name, final String signature, final String superName,
            final String[] interfaces) {
        this.version = version;
        this.access = access;
        this.name = newClass(name);
        thisName = name;
        if (ClassReader.SIGNATURES && signature != null) {
            this.signature = newUTF8(signature);
        }
        this.superName = superName == null ? 0 : newClass(superName);
        if (interfaces != null && interfaces.length > 0) {
            interfaceCount = interfaces.length;
            this.interfaces = new int[interfaceCount];
            for (int i = 0; i < interfaceCount; ++i) {
                this.interfaces[i] = newClass(interfaces[i]);
            }
        }
    }

通过 newClass(...) 方法保证类名,父类名,接口类名都在常量池中存在,并获取对应的常量池索引。

其他的visitSource,visitOuterClassvisitInnerClass 大体都是如此,保证在常量池中有对应项,并获取索引。

   @Override
    public final void visitSource(final String file, final String debug) {
        if (file != null) {
            sourceFile = newUTF8(file);
        }
        if (debug != null) {
            sourceDebug = new ByteVector().encodeUTF8(debug, 0,
                    Integer.MAX_VALUE);
        }
    }

   @Override
    public final void visitOuterClass(final String owner, final String name,
            final String desc) {
        enclosingMethodOwner = newClass(owner);
        if (name != null && desc != null) {
            enclosingMethod = newNameType(name, desc);
        }
    }

    @Override
    public final void visitInnerClass(final String name,
            final String outerName, final String innerName, final int access) {
        if (innerClasses == null) {
            innerClasses = new ByteVector();
        }
        Item nameItem = newClassItem(name);
        if (nameItem.intVal == 0) {
            ++innerClassesCount;
            innerClasses.putShort(nameItem.index);
            innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
            innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
            innerClasses.putShort(access);
            nameItem.intVal = innerClassesCount;
        } else {
            // Compare the inner classes entry nameItem.intVal - 1 with the
            // arguments of this method and throw an exception if there is a
            // difference?
        }
    }

4.3.2 visitAnnotation(...)

  public final AnnotationVisitor visitAnnotation(final String desc,
            final boolean visible) {
        if (!ClassReader.ANNOTATIONS) {
            return null;
        }
        ByteVector bv = new ByteVector();
        // write type, and reserve space for values count
        bv.putShort(newUTF8(desc)).putShort(0);
        AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
        if (visible) {
            aw.next = anns;
            anns = aw;
        } else {
            aw.next = ianns;
            ianns = aw;
        }
        return aw;
    }
  • bv.putShort(newUTF8(desc)).putShort(0).putShort(0)作用就是先将属性大小 size 位置占了。
  • iannsanns 使用链表记录当前类所有类注解。

4.3.3 visitField(...)

    @Override
    public final FieldVisitor visitField(final int access, final String name,
            final String desc, final String signature, final Object value) {
        return new FieldWriter(this, access, name, desc, signature, value);
    }

通过 ClassWriterfirstFieldlastField 来记录当前类所有的字段。

4.3.4 visitMethod(...)

    public final MethodVisitor visitMethod(final int access, final String name,
            final String desc, final String signature, final String[] exceptions) {
        return new MethodWriter(this, access, name, desc, signature,
                exceptions, computeMaxs, computeFrames);
    }

通过 ClassWriterfirstMethodlastMethod 来记录当前类所有的方法。

4.4 toByteArray 方法

生成字节码的方法,下面我们来一步一步分析。

    public byte[] toByteArray() {
        if (index > 0xFFFF) {
            throw new RuntimeException("Class file too large!");
        }
        int size = 24 + 2 * interfaceCount;
        int nbFields = 0;
        FieldWriter fb = firstField;
        while (fb != null) {
            ++nbFields;
            size += fb.getSize();
            fb = (FieldWriter) fb.fv;
        }
        int nbMethods = 0;
        .......
       if (attrs != null) {
            attributeCount += attrs.getCount();
            size += attrs.getSize(this, null, 0, -1, -1);
        }
        size += pool.length;
      .....
}

代码到这里都是为了计算生成字节码的大小size

    public byte[] toByteArray() {
        .......
         /**
         * ClassFile {
         *    u4 magic;
         *    u2 minor_version;
         *    u2 major_version;
         *    u2 constant_pool_count;
         *    cp_info constant_pool[constant_pool_count-1];
         *    u2 access_flags;
         *    u2 this_class;
         *    u2 super_class;
         *    u2 interfaces_count;
         *    u2 interfaces[interfaces_count];
         *    u2 fields_count;
         *    field_info fields[fields_count];
         *    u2 methods_count;
         *    method_info methods[methods_count];
         *    u2 attributes_count;
         *    attribute_info attributes[attributes_count];
         * }
         */
        ByteVector out = new ByteVector(size);
        out.putInt(0xCAFEBABE).putInt(version);
        out.putShort(index).putByteArray(pool.data, 0, pool.length);
        int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
                | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
        out.putShort(access & ~mask).putShort(name).putShort(superName);
        out.putShort(interfaceCount);
        for (int i = 0; i < interfaceCount; ++i) {
            out.putShort(interfaces[i]);
        }
        out.putShort(nbFields);
        fb = firstField;
        while (fb != null) {
            fb.put(out);
            fb = (FieldWriter) fb.fv;
        }
        out.putShort(nbMethods);
        mb = firstMethod;
        while (mb != null) {
            mb.put(out);
            mb = (MethodWriter) mb.mv;
        }
       out.putShort(attributeCount);
        if (bootstrapMethods != null) {
            out.putShort(newUTF8("BootstrapMethods"));
            out.putInt(bootstrapMethods.length + 2).putShort(
                    bootstrapMethodsCount);
            out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
        }
        .......
    }

按照ClassFile 格式,写入字节码数据。

五. 例子

5.1 读取字节码文件

public class T {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader(T.class.getName());
        classReader.accept(new ClassVisitor(ASM5) {
            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                System.out.println(String.format("version:[%s] access:[%s] name:[%s] signature:[%s]  superName:[%s] interfaces:[%s]",
                        version, access, name, signature, superName, Arrays.toString(interfaces)));
            }

            @Override
            public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
                System.out.println(String.format("visitField access:[%s] name:[%s] desc:[%s] signature:[%s] value:[%s]",
                        access, name, desc, signature, value));
                return super.visitField(access, name, desc, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                System.out.println(String.format("visitMethod access:[%s] name:[%s] desc:[%s] signature:[%s] exceptions:[%s]",
                        access, name, desc, signature, Arrays.toString(exceptions)));
                return super.visitMethod(access, name, desc, signature, exceptions);
            }
        }, 0);

    }

运行结果:

version:[52] access:[33] name:[com/zhang/bytecode/ams/_3/T] signature:[null]  superName:[java/lang/Object] interfaces:[[]]
visitField access:[2] name:[name] desc:[Ljava/lang/String;] signature:[null] value:[null]
visitMethod access:[1] name:[<init>] desc:[()V] signature:[null] exceptions:[null]
visitMethod access:[1] name:[getName] desc:[()Ljava/lang/String;] signature:[null] exceptions:[null]
visitMethod access:[1] name:[setName] desc:[(Ljava/lang/String;)V] signature:[null] exceptions:[null]

ASM 也提供了非常方便地查看类信息的工具类,分别是 TraceClassVisitorASMifier

5.1.1 TraceClassVisitor

public class T {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader(T.class.getName());
        classReader.accept(new TraceClassVisitor(new PrintWriter(System.out)),0);
    }
}

将打印结果直接输出到控制台 System.out

运行结果如下:

// class version 52.0 (52)
// access flags 0x21
public class com/zhang/bytecode/ams/_2/T {

  // compiled from: T.java
  // access flags 0x2
  private Ljava/lang/String; name

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 6 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public getName()Ljava/lang/String;
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD com/zhang/bytecode/ams/_2/T.name : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public setName(Ljava/lang/String;)V
   L0
    LINENUMBER 14 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/zhang/bytecode/ams/_2/T.name : Ljava/lang/String;
   L1
    LINENUMBER 15 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/zhang/bytecode/ams/_2/T; L0 L2 0
    LOCALVARIABLE name Ljava/lang/String; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

很清晰地打印出类的信息,包括方法中的指令集。例如 setName(name) 方法:

  • public setName(Ljava/lang/String;)V 方法的访问标志,方法名和方法描述符。
  • L0 其实对应了 this.name = name; 这句代码。
  • L1 对应着方法默认无返回值的 return,虽然源码中没有,但是javac 编译器会帮我们添加到字节码文件的方法中。
  • L2 表示方法Code 属性指令集最后一个指令的地址,用来计算LocalVariableTableLocalVariableTypeTable 属性的。

5.1.2 ASMifier

public class T {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader(T.class.getName());
        classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)),0);
    }
}

ASMifier 对象作为参数传递给 TraceClassVisitor 实例,这样我们就可以得到如下结果:

import java.util.*;

import zhang.asm.*;

public class TDump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/zhang/bytecode/ams/_2/T", null, "java/lang/Object", null);

        cw.visitSource("T.java", null);

        {
            fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(6, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(10, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, "com/zhang/bytecode/ams/_2/T", "name", "Ljava/lang/String;");
            mv.visitInsn(ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(14, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitFieldInsn(PUTFIELD, "com/zhang/bytecode/ams/_2/T", "name", "Ljava/lang/String;");
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(15, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", "Lcom/zhang/bytecode/ams/_2/T;", null, l0, l2, 0);
            mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

使用了ASMifier 对象之后,输出的结果不在是类信息,而是如何使用 ASM 生成 T 这个字节码类。

5.2 生成简单字节码

public class Test {
    public static void main(String[] args) throws Exception {
        ClassWriter classWriter = new ClassWriter(ASM5);
        classWriter.visit(52, ACC_PUBLIC, "com/zhang/bytecode/ams/_3/TG",null, "java/lang/Object", null);
        classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "age", Type.getDescriptor(int.class), null, 10);
        classWriter.visitField(ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);
        
        Files.write(Paths.get("TG.class"), classWriter.toByteArray());
    }
}

生成后,使用 javap -v -p TG.class 命令:

Classfile /Users/zhangxinhao/work/java/test/example/TG.class
  Last modified 2021-12-19; size 163 bytes
  MD5 checksum b015e3451e79ceda7d55f10215b44595
public class com.zhang.bytecode.ams._3.TG
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC
Constant pool:
   #1 = Utf8               com/zhang/bytecode/ams/_3/TG
   #2 = Class              #1             // com/zhang/bytecode/ams/_3/TG
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               age
   #6 = Utf8               I
   #7 = Integer            10
   #8 = Utf8               name
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               ConstantValue
{
  public static final int age;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 10

  private java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

}

你会发现一个巨大问题,它居然没有构造函数,是没有办法创建对象的,那么我们将构造方法加上。

public class CustomClassLoader extends ClassLoader {

    public static final CustomClassLoader INSTANCE = new CustomClassLoader();

    public Class defineClass(String name, byte[] b) {
        return defineClass(name, b, 0, b.length);
    }
}

public class Test {

    public static void main(String[] args) throws Exception {
        ClassWriter classWriter = new ClassWriter(ASM5);
        classWriter.visit(52, ACC_PUBLIC, "com/zhang/bytecode/ams/_3/TG",null, "java/lang/Object", null);
        classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "age", Type.getDescriptor(int.class), null, 10);
        classWriter.visitField(ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);

        MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        methodVisitor.visitInsn(RETURN);
        methodVisitor.visitMaxs(1, 1);
        methodVisitor.visitEnd();

        byte[] bytes = classWriter.toByteArray();
        Files.write(Paths.get("TG.class"), bytes);

        Class clazz = CustomClassLoader.INSTANCE.defineClass("com.zhang.bytecode.ams._3.TG", bytes);
        System.out.println(clazz);
        Object obj = clazz.newInstance();
        System.out.println(obj);
    }
}

运行结果

class com.zhang.bytecode.ams._3.TG
com.zhang.bytecode.ams._3.TG@55f96302

我们自定义的class 创建成功。

使用 javap -v -p TG.class 命令结果是:

Classfile /Users/zhangxinhao/work/java/test/example/TG.class
  Last modified 2021-12-19; size 226 bytes
  MD5 checksum 3cc7120fe6f4573bfac44d4f056c8660
public class com.zhang.bytecode.ams._3.TG
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC
Constant pool:
   #1 = Utf8               com/zhang/bytecode/ams/_3/TG
   #2 = Class              #1             // com/zhang/bytecode/ams/_3/TG
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               age
   #6 = Utf8               I
   #7 = Integer            10
   #8 = Utf8               name
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = NameAndType        #10:#11        // "<init>":()V
  #13 = Methodref          #4.#12         // java/lang/Object."<init>":()V
  #14 = Utf8               ConstantValue
  #15 = Utf8               Code
{
  public static final int age;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 10

  private java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  public com.zhang.bytecode.ams._3.TG();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #13                 // Method java/lang/Object."<init>":()V
         4: return
}

5.3 修改已编译字节码文件中方法

假如我们现在需要打印每个方法执行时间,那么怎么修改字节码的方式搞定。

class MyMethodVisitor extends MethodVisitor {
    int firstLocal;
    int insertCount = 2; // long 类型是 2
    int lastMaxVar;
    public MyMethodVisitor(int access, String desc, MethodVisitor mv) {
        super(ASM5, mv);
        Type[] args = Type.getArgumentTypes(desc);
        // 实例方法有 this
        firstLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
        for (int i = 0; i < args.length; i++) {
            firstLocal += args[i].getSize();
        }
        lastMaxVar = firstLocal;
    }

    @Override
    public void visitCode() {
        super.visitCode();
        visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        super.visitVarInsn(LSTORE, firstLocal);
        lastMaxVar += 2;
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        if (var >= firstLocal) {
            var += insertCount;
        }
        if (var > lastMaxVar) {
            lastMaxVar = var;
        }
        super.visitVarInsn(opcode, var);
    }

    @Override
    public void visitInsn(int opcode) {
        if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
            visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            super.visitVarInsn(LLOAD, firstLocal);
            visitInsn(LSUB);
            visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
        }
        super.visitInsn(opcode);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack , maxLocals);
    }
}

    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader(T.class.getName());
        ClassWriter classWriter = new ClassWriter(COMPUTE_FRAMES);
        classReader.accept(new ClassVisitor(ASM5, classWriter) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                return new MyMethodVisitor(access, desc,
                        super.visitMethod(access, name, desc, signature, exceptions));
            }
        }, EXPAND_FRAMES);

        byte[] bytes = classWriter.toByteArray();

        Class clazz = CustomClassLoader.INSTANCE.defineClass(T.class.getName(), bytes);
        Object object = clazz.newInstance();

        for (Method method : clazz.getDeclaredMethods()) {
            System.out.print("方法[" + method.getName() + "]:\t");
            method.setAccessible(true);
            method.invoke(object);
        }
    }


public class T {

    private void t1() {
    }
    private void t2() throws InterruptedException {
        Thread.sleep(1000);
    }
}
  • 通过 ClassReader 读取原字节码文件 T.class,然后自定义MethodVisitor 对象,向方法中添加自定义指令。
  • visitCode() 在方法刚开始时调用,我们插入 long var1 = System.currentTimeMillis(); 语句;这里非常需要注意的一点,就是我们向方法局部变量表中插入了一个局部变量 var1,类型是 long,位置就是在方法参数变量后。
  • 因为改变了局部变量表,所以要复写visitVarInsn(...) 方法,让原字节码文件中的方法,访问局部变量正确。
  • 最后复写 visitInsn(...) 方法,再跳出方法指令之前,插入 System.out.println(System.currentTimeMillis() - var1);语句。
  • 注意 new ClassWriter(COMPUTE_FRAMES) 一定要使用 COMPUTE_FRAMES,动态计算StackMapTable,不然方法中有程序跳转指令(例如 goto),那么原来的StackMapTable就是错误的,导致方法验证不通过。

运行结果:

0
方法[t1]: 0
方法[t2]: 1004
方法[t3]: 2002

得到的反编译字节码文件

public class T {
    public T() {
        long var1 = System.currentTimeMillis();
        super();
        System.out.println(System.currentTimeMillis() - var1);
    }

    private void t1() {
        long var1 = System.currentTimeMillis();
        System.out.println(System.currentTimeMillis() - var1);
    }

    private void t2() throws InterruptedException {
        long var1 = System.currentTimeMillis();
        Thread.sleep(1000L);
        System.out.println(System.currentTimeMillis() - var1);
    }

    private void t3() {
        long var1 = System.currentTimeMillis();

        try {
            Thread.sleep(2000L);
        } catch (InterruptedException var4) {
            var4.printStackTrace();
        }

        System.out.println(System.currentTimeMillis() - var1);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容