JVM_ASM技術(shù)原理分析

在前面的文章中,我們分析了Class 這個字節(jié)碼文件的格式锯茄,知道了字節(jié)碼的作用厢塘,那么我們就可以直接生成字節(jié)碼文件,加載到當(dāng)前的 JVM 中運行肌幽,這個在AOP 場景中經(jīng)常用到晚碾。
當(dāng)然直接手動寫字節(jié)碼難度比較大,太過麻煩喂急。這里就介紹一個非常重要也非常高效的字節(jié)碼生成框架ASM格嘁。

Java8Lambda 表達式,也是通過ASM動態(tài)生成類的廊移。

一. ASM 介紹

ASM可以高效地生成.class字節(jié)碼文件糕簿,分為兩種方式:

  • 核心API : 流式讀取字節(jié)碼文件內(nèi)容涣易,并通過訪問器的模式,將讀取到內(nèi)容數(shù)據(jù)暴露出去冶伞,類比解析XML文件中的SAX方式新症。
  • 樹形API : 底層就是采用核心API ,只不過它將讀取到的內(nèi)容節(jié)點响禽,保存在內(nèi)存中猫妙,形成一個樹形結(jié)構(gòu)挤茄,類比解析XML文件中的DOM方式。

因此我們只要了解核心API 原理就行了,主要就是以下幾個方面:

  • 各種訪問器Visitor谈秫,用來接收到字節(jié)碼中的各部分?jǐn)?shù)據(jù)萍倡。
  • 各種寫入器Writer膝昆,它們是Visitor子類以清,接收到數(shù)據(jù),然后寫入到字節(jié)數(shù)組中贮竟,生成一個字節(jié)碼文件丽焊。
  • ClassReader ,它比較特殊,就是用來讀取一個字節(jié)碼文件咕别,并且可以將字節(jié)碼中的各部分?jǐn)?shù)據(jù)發(fā)送給各個訪問器Visitor技健。

因為我們之前介紹 JVM8 的字節(jié)碼文件格式,因此我們選擇 ASM 也是適配 JVM8惰拱,其實就是openjdk8 中源碼雌贱。

如果對字節(jié)碼中結(jié)構(gòu)和信息不清楚的,請先閱讀這些文章:字節(jié)碼文件(ClassFile)詳解,字節(jié)碼的屬性,指令集偿短。

二. 訪問器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 中方法實現(xiàn)都是調(diào)用 ClassVisitor cv 這個成員變量對應(yīng)方法。

2.1.2 ClassFile 結(jié)構(gòu)

首先讓我們回憶一下昔逗,ClassFile 文件結(jié)構(gòu):

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];
}

分為魔數(shù)降传,版本,常量池纤子,訪問標(biāo)志搬瑰,類名,繼承的父類名控硼,實現(xiàn)的接口集合,字段列表艾少,方法列表和ClassFile的屬性列表卡乾。

2.1.3 ClassVisitor 中的方法

方法的調(diào)用順序如下:

 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)

    字節(jié)碼文件的版本version,訪問標(biāo)志access,類名name,類泛型簽名屬性signature,繼承的父類名 superName 和 實現(xiàn)的接口集合interfaces

  2. void visitSource(String source, String debug)

    字節(jié)碼文件中SourceFileSourceDebugExtension 這兩個屬性信息缚够。

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

    字節(jié)碼文件中 EnclosingMethod 屬性信息幔妨。

  4. AnnotationVisitor visitAnnotation(String desc, boolean visible)
    • 字節(jié)碼文件中 RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations屬性信息鹦赎。
    • desc 表示注解類名的描述符,visible 表示這個注解可不可見误堡。
  5. `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
    • 字節(jié)碼文件中 RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations屬性信息古话。
    • desc 表示注解類名的描述符,visible 表示這個注解可不可見锁施。
  6. void visitAttribute(Attribute attr)

    這個是字節(jié)碼文件中自定義的屬性陪踩,不是當(dāng)前 JVM 規(guī)定的屬性值。

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

    字節(jié)碼文件中 InnerClasses 屬性信息悉抵。

  8. FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
    • 字節(jié)碼文件中字段列表肩狂。
    • 這里包含了字段的訪問標(biāo)志access,字段名name,字段描述符 desc, 字段簽名信息signature和字段常量屬性ConstantValue的值 value
  9. MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
    • 字節(jié)碼文件中方法列表姥饰。
    • 這里包含了方法的訪問標(biāo)志access,方法名name,方法描述符 desc, 方法簽名信息signature 和方法拋出異常列表exceptions傻谁。
  10. void visitEnd()

表示字節(jié)碼文件訪問結(jié)束。

2.2 注解訪問器AnnotationVisitor

AnnotationVisitor主要是用來接收注解相關(guān)屬性的列粪,主要就是 RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault,RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations這七個屬性了审磁。

注解的類名信息已經(jīng)在 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 成員變量,來實現(xiàn)AnnotationVisitor 方法掺逼。

2.2.2 annotation 結(jié)構(gòu)

還記得注解的數(shù)據(jù)結(jié)構(gòu)么:

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 和數(shù)組類型array_value赘那。

2.2.3 AnnotationVisitor 中的方法

方法調(diào)用順序如下:

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

    注解中數(shù)組類型鍵值對信息拱礁,也返回一個 AnnotationVisitor
    其實數(shù)組類型和注解類型的區(qū)別辕漂,注解類型有多個鍵值對集合呢灶,而數(shù)組類型只有鍵值的列表,沒有鍵name钉嘹。

  5. void visitEnd()

    表示注解訪問結(jié)束鸯乃。

2.3 字段訪問器 FieldVisitor

  • 字段的訪問標(biāo)志access,字段名name,字段描述符 desc, 字段簽名信息signature和字段常量屬性ConstantValue的值 value 已經(jīng)在 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 結(jié)構(gòu)

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

字段中屬性有:

  • ConstantValue : 通過 final 關(guān)鍵字修飾的字段對應(yīng)的常量值缨睡。
  • Synthetic : 標(biāo)志字段鸟悴,方法和類由編譯器自動生成。
  • Deprecated : 聲明字段奖年,方法和類將棄用细诸。
  • Signature : 記錄字段,方法和類的泛型信息陋守。
  • RuntimeVisibleAnnotations : 可見的字段震贵,方法和類注解。
  • RuntimeInvisibleAnnotations : 不可見的字段嗅义,方法和類注解屏歹。
  • RuntimeVisibleTypeAnnotations : 可見的類型注解,主要用于實現(xiàn) JSR 308之碗。
  • RuntimeInvisibleTypeAnnotations : 不可見的類型注解蝙眶,主要用于實現(xiàn) JSR 308

2.3.3 FieldVisitor 中的方法

方法調(diào)用順序如下:

( visitAnnotation | visitTypeAnnotation | visitAttribute )*
visitEnd
  1. AnnotationVisitor visitAnnotation(String desc, boolean visible)
    • 字節(jié)碼文件中 RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations屬性信息褪那。
    • desc 表示注解類名的描述符幽纷,visible 表示這個注解可不可見。
  2. `AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible)
    • 字節(jié)碼文件中 RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations屬性信息博敬。
    • desc 表示注解類名的描述符友浸,visible 表示這個注解可不可見。
  3. void visitAttribute(Attribute attr)

    這個是字節(jié)碼文件中自定義的屬性偏窝,不是當(dāng)前 JVM 規(guī)定的屬性值收恢。

  4. void visitEnd()

    表示字段訪問結(jié)束。

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 結(jié)構(gòu)

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

方法中的屬性有:

  • Code : 方法對應(yīng)字節(jié)碼指令祭往。
  • Exceptions : 方法拋出的異常列表伦意。
  • RuntimeVisibleParameterAnnotations : 可見的方法參數(shù)注解。
  • RuntimeInvisibleParameterAnnotations : 不可見的方法參數(shù)注解硼补。
  • AnnotationDefault : 記錄注解類元素默認值驮肉。
  • MethodParameters : 將方法參數(shù)參數(shù)名編譯到字節(jié)碼文件中(編譯時加上 -parameters 參數(shù))。
  • Synthetic : 標(biāo)志字段已骇,方法和類由編譯器自動生成离钝。
  • Deprecated : 聲明字段,方法和類將棄用褪储。
  • Signature : 記錄字段卵渴,方法和類的泛型信息。
  • RuntimeVisibleAnnotations : 可見的字段乱豆,方法和類注解奖恰。
  • RuntimeInvisibleAnnotations : 不可見的字段,方法和類注解宛裕。
  • RuntimeVisibleTypeAnnotations : 可見的類型注解瑟啃,主要用于實現(xiàn) JSR 308
  • RuntimeInvisibleTypeAnnotations : 不可見的類型注解揩尸,主要用于實現(xiàn) JSR 308蛹屿。

2.4.3 MethodVisitor 中的方法

方法調(diào)用順序如下

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

可以看出 MethodVisitor 中的方法比較復(fù)雜,主要是因為 Code 屬性數(shù)據(jù)岩榆,需要生成方法指令集错负。

  1. void visitParameter(String name, int access)

    方法中MethodParameters 屬性,獲取方法參數(shù)的參數(shù)名name和參數(shù)訪問標(biāo)志access勇边。

  2. AnnotationVisitor visitAnnotationDefault()

    方法中AnnotationDefault 屬性犹撒,只會出現(xiàn)在注解類中,注解類方法默認返回值信息粒褒。

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

    方法訪問結(jié)束奕坟。

2.4.3 Code 屬性相關(guān)方法

我們知道方法的操作其實就是對應(yīng)一個一個的JVM 指令祥款,而這些指令就存在 Code 屬性中。

2.4.3.1 Code 屬性結(jié)構(gòu)

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 源碼中方法中字節(jié)指令和行號對應(yīng)關(guān)系月杉。
  • LocalVariableTable : 方法中局部變量描述刃跛。
  • LocalVariableTypeTable : 方法中泛型局部變量描述。
  • StackMapTable : 記錄數(shù)據(jù)供類型檢查驗證器檢查苛萎,來驗證方法局部變量表和操作數(shù)棧所需類型是否匹配桨昙。
  • RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations

2.4.3.2 Code 屬性對應(yīng)方法

方法流程如下:

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 屬性腌歉。

    關(guān)于 StackMapTable 詳細說明請看字節(jié)碼的屬性蛙酪。

  3. void visitInsn(int opcode) : 訪問零操作數(shù)指令

    這些指令有 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) : 訪問操作數(shù)就是整型值的指令。
    • 也就是說這個操作數(shù)不代表索引這些究履,比如 BIPUSH 指令滤否,就是將一個byte 類型數(shù)放入操作數(shù)棧。
    • 這些指令有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) : 訪問動態(tài)方法指令。
    • 動態(tài)方法名name,動態(tài)方法描述desc,引導(dǎo)方法處理器bsm 和引導(dǎo)方法處理器參數(shù)bsmArgs浙宜。
    • 這個指令就是 invokedynamic官辽。
  10. visitJumpInsn(int opcode, Label label) : 訪問程序跳轉(zhuǎn)指令。

    這些指令包括 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 記錄當(dāng)前 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 中的數(shù)據(jù)。
  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 可以讀取字節(jié)碼文件蝠筑,并分發(fā)給各個訪問器 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
        // 字節(jié)碼版本超過 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個字節(jié)包括  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 中保存常量數(shù)據(jù)開始位置
            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 類型常量的字節(jié)數(shù)已球。
        maxStringLength = max;
        // the class header information starts just after the constant pool
        // header 就是 access_flags 開始位置
        header = index;
    }

主要就是獲取字節(jié)碼文件的字節(jié)數(shù)組byte[] b, 然后解析字節(jié)碼的常量池臣镣,得到三個成員變量:

  • items : 記錄常量池中所有常量數(shù)據(jù)在字節(jié)數(shù)組b 的位置,方便常量數(shù)據(jù)智亮。
  • strings : 用來緩存 UTF8 類型常量的字符串忆某。
  • header : 字節(jié)碼中 access_flags 開始位置。

3.2 accept 方法

accept 方法就是接收一個 ClassVisitor 對象阔蛉,將字節(jié)碼文件中的數(shù)據(jù)發(fā)送到 類訪問器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;
        }
        .......
}

首先讀取了字節(jié)碼文件中的訪問標(biāo)志 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() 方法獲取類屬性列表的開始位置状原。
  • 然后獲取屬性個數(shù)聋呢,通過遍歷獲取所有類屬性值。
   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) {
        // 獲取注解鍵值對個數(shù)削锰,或者鍵值的個數(shù),如果 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;
    }

這里有一點不好器贩,應(yīng)該增加 readAnnotationElementValue 方法颅夺,這樣就可以區(qū)分 { u2 element_name_index; element_value value;}element_value value 區(qū)別,就不需要通過 named 進行區(qū)別了蛹稍。

    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;
    }

讀取注解不同類型鍵值對的數(shù)據(jù)吧黄,值得注意的是讀取 array 類型時,它選擇在當(dāng)前方法中直接通過循環(huán)處理稳摄,而不是使用遞歸稚字。

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;
        ......
}

先讀取字段的訪問標(biāo)志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 中方法調(diào)用順序。

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;
        .......
   }

先讀取方法的訪問標(biāo)志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 中方法調(diào)用順序,不過 Code 屬性相關(guān)方法国夜,還是通過 readCode(mv, context, code) 方法決定减噪。
  • if (WRITER && mv instanceof MethodWriter) 這個判斷主要是為了沒有修改的方法可以直接復(fù)制,而不用重新計算车吹。

3.6 readCode 方法

這個方法比較復(fù)雜筹裕,源碼有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 屬性的操作數(shù)棧最大深度maxStack, 局部變量表大小maxLocals朝卒,指令集的大小codeLength,指令開始位置codeStart 和指令結(jié)束位置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 循環(huán)為了記錄程序跳轉(zhuǎn)指令對應(yīng)的指令地址(即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 的代碼塊,即異常處理丈咐,對應(yīng)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 中的指令集棵逊,并調(diào)用MethodVisitor中相關(guān)visitXInsn 方法伤疙。

四. 類寫入器ClassWriter

ClassReader 只能讀取已經(jīng)編譯好的字節(jié)碼文件,但是如果我們想要生成一個字節(jié)碼文件辆影,那么就必須使用這個類寫入器ClassWriter了徒像。

創(chuàng)建ClassWriter 必須傳遞一個 flags 標(biāo)志,這個 flags 可以選擇三個值秸歧,分別是:

  • 0 : 表示不做特殊處理厨姚。
  • COMPUTE_MAXS : 動態(tài)計算方法的 Code 屬性中的max_stackmax_locals。如果我們改變了 Code 屬性中指令键菱,那么就可能需要計算max_stackmax_locals值了谬墙,否則JVM會執(zhí)行方法失敗今布。
  • COMPUTE_FRAMES : 動態(tài)計算方法的StackMapTable,如果方法中有程序跳轉(zhuǎn)指令拭抬,那么必須要有StackMapTable部默,否則JVM方法驗證不通過,拒絕執(zhí)行造虎。

我們仔細想一下字節(jié)碼文件中傅蹂,最難處理的是那部分呢?

肯定就是常量池了算凿,因為我們的常量份蝴,類引用,字段引用氓轰,方法引用等等婚夫,都必須先在常量池中定義,而用到的地方只需要使用對應(yīng)常量池索引就可以了署鸡。

那么我們先看看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 創(chuàng)建常量方法

通過一系列new 方法創(chuàng)建常量蟀伸。

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

根據(jù) cst 類型調(diào)用不同的 new 方法創(chuàng)建對應(yīng)的常量項。

    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 對應(yīng)的常量。
  • 如果哈希表中沒有這個常量項衰猛,那么就創(chuàng)建迟蜜。先將常量存到常量池pool 中,然后創(chuàng)建對應(yīng)常量項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(...) 方法保證類名娜睛,父類名髓霞,接口類名都在常量池中存在,并獲取對應(yīng)的常量池索引畦戒。

其他的visitSource,visitOuterClassvisitInnerClass 大體都是如此方库,保證在常量池中有對應(yīng)項,并獲取索引障斋。

   @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 使用鏈表記錄當(dāng)前類所有類注解。

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 來記錄當(dāng)前類所有的字段。

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 來記錄當(dāng)前類所有的方法。

4.4 toByteArray 方法

生成字節(jié)碼的方法微饥,下面我們來一步一步分析。

    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;
      .....
}

代碼到這里都是為了計算生成字節(jié)碼的大小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 格式,寫入字節(jié)碼數(shù)據(jù)涧团。

五. 例子

5.1 讀取字節(jié)碼文件

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

    }

運行結(jié)果:

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

將打印結(jié)果直接輸出到控制臺 System.out泌绣。

運行結(jié)果如下:

// 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 方法的訪問標(biāo)志阿迈,方法名和方法描述符。
  • L0 其實對應(yīng)了 this.name = name; 這句代碼轧叽。
  • L1 對應(yīng)著方法默認無返回值的 return苗沧,雖然源碼中沒有,但是javac 編譯器會幫我們添加到字節(jié)碼文件的方法中炭晒。
  • 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 對象作為參數(shù)傳遞給 TraceClassVisitor 實例网严,這樣我們就可以得到如下結(jié)果:

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 對象之后识樱,輸出的結(jié)果不在是類信息,而是如何使用 ASM 生成 T 這個字節(jié)碼類震束。

5.2 生成簡單字節(jié)碼

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

}

你會發(fā)現(xiàn)一個巨大問題,它居然沒有構(gòu)造函數(shù)垢村,是沒有辦法創(chuàng)建對象的割疾,那么我們將構(gòu)造方法加上。

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

運行結(jié)果

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

我們自定義的class 創(chuàng)建成功嘉栓。

使用 javap -v -p TG.class 命令結(jié)果是:

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 修改已編譯字節(jié)碼文件中方法

假如我們現(xiàn)在需要打印每個方法執(zhí)行時間宏榕,那么怎么修改字節(jié)碼的方式搞定驰凛。

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 讀取原字節(jié)碼文件 T.class,然后自定義MethodVisitor 對象担扑,向方法中添加自定義指令恰响。
  • visitCode() 在方法剛開始時調(diào)用,我們插入 long var1 = System.currentTimeMillis(); 語句涌献;這里非常需要注意的一點胚宦,就是我們向方法局部變量表中插入了一個局部變量 var1,類型是 long燕垃,位置就是在方法參數(shù)變量后枢劝。
  • 因為改變了局部變量表,所以要復(fù)寫visitVarInsn(...) 方法卜壕,讓原字節(jié)碼文件中的方法您旁,訪問局部變量正確。
  • 最后復(fù)寫 visitInsn(...) 方法轴捎,再跳出方法指令之前鹤盒,插入 System.out.println(System.currentTimeMillis() - var1);語句。
  • 注意 new ClassWriter(COMPUTE_FRAMES) 一定要使用 COMPUTE_FRAMES侦副,動態(tài)計算StackMapTable侦锯,不然方法中有程序跳轉(zhuǎn)指令(例如 goto),那么原來的StackMapTable就是錯誤的秦驯,導(dǎo)致方法驗證不通過尺碰。

運行結(jié)果:

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

得到的反編譯字節(jié)碼文件

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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市译隘,隨后出現(xiàn)的幾起案子亲桥,更是在濱河造成了極大的恐慌,老刑警劉巖固耘,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件题篷,死亡現(xiàn)場離奇詭異,居然都是意外死亡玻驻,警方通過查閱死者的電腦和手機悼凑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璧瞬,“玉大人户辫,你說我怎么就攤上這事∴惋保” “怎么了渔欢?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘟忱。 經(jīng)常有香客問我奥额,道長苫幢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任垫挨,我火速辦了婚禮韩肝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘九榔。我一直安慰自己哀峻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布哲泊。 她就那樣靜靜地躺著剩蟀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪切威。 梳的紋絲不亂的頭發(fā)上育特,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音先朦,去河邊找鬼缰冤。 笑死,一個胖子當(dāng)著我的面吹牛烙无,可吹牛的內(nèi)容都是我干的锋谐。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼截酷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乾戏?” 一聲冷哼從身側(cè)響起迂苛,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鼓择,沒想到半個月后三幻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡呐能,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年念搬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摆出。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡朗徊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偎漫,到底是詐尸還是另有隱情爷恳,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布象踊,位于F島的核電站温亲,受9級特大地震影響棚壁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栈虚,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一袖外、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魂务,春花似錦曼验、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至相艇,卻和暖如春颖杏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坛芽。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工留储, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咙轩。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓获讳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親活喊。 傳聞我的和親對象是個殘疾皇子丐膝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容