• ASM:(5)ClassVisitor和ClassWriter


    原文:https://lsieun.github.io/java-asm-01/class-visitor-intro.html

    ClassVisitor类

    在ASM Core API中,最重要的三个类就是ClassReaderClassVisitorClassWriter类。在进行Class Generation操作的时候,ClassVisitorClassWriter这两个类起着重要作用,而并不需要ClassReader类的参与。在本文当中,我们将对ClassVisitor类进行介绍。

    image-20220408093023646

    class info

    第一个部分,ClassVisitor是一个抽象类。 由于ClassVisitor类是一个abstract类,所以不能直接使用new关键字创建ClassVisitor对象。

    public abstract class ClassVisitor {
    }
    

    同时,由于ClassVisitor类是一个abstract类,要想使用它,就必须有具体的子类来继承它。

    第一个比较常见的ClassVisitor子类是ClassWriter类,属于Core API:

    public class ClassWriter extends ClassVisitor {
    }
    

    第二个比较常见的ClassVisitor子类是ClassNode类,属于Tree API:

    public class ClassNode extends ClassVisitor {
    }
    

    fields

    第二个部分,ClassVisitor类定义的字段有哪些。

    public abstract class ClassVisitor {
        protected final int api;
        protected ClassVisitor cv;
    }
    
    • api字段:它是一个int类型的数据,指出了当前使用的ASM版本,其可取值为Opcodes.ASM4~Opcodes.ASM9。我们使用的ASM版本是9.0,因此我们在给api字段赋值的时候,选择Opcodes.ASM9就可以了。
    • cv字段:它是一个ClassVisitor类型的数据,它的作用是将多个ClassVisitor串连起来。

    image-20220408102754033

    constructors

    第三个部分,ClassVisitor类定义的构造方法

    public abstract class ClassVisitor {
        public ClassVisitor(final int api) {
            this(api, null);
        }
    
        public ClassVisitor(final int api, final ClassVisitor classVisitor) {
            this.api = api;
            this.cv = classVisitor;
        }
    }
    

    methods

    在ASM当中,使用到了Visitor Pattern(访问者模式),所以ClassVisitor当中许多的visitXxx()方法。

    虽然,在ClassVisitor类当中,有许多visitXxx()方法,但是,我们只需要关注这4个方法:visit()visitField()visitMethod()visitEnd()

    为什么只关注这4个方法呢?因为这4个方法是ClassVisitor类的精髓或骨架,在这个“骨架”的基础上,其它的visitXxx()都容易扩展;同时,将visitXxx()方法缩减至4个,也能减少学习过程中的认知负担,学起来更容易。

    public abstract class ClassVisitor {
        public void visit(
            final int version,
            final int access,
            final String name,
            final String signature,
            final String superName,
            final String[] interfaces);
        public FieldVisitor visitField( // 访问字段
            final int access,
            final String name,
            final String descriptor,
            final String signature,
            final Object value);
        public MethodVisitor visitMethod( // 访问方法
            final int access,
            final String name,
            final String descriptor,
            final String signature,
            final String[] exceptions);
        public void visitEnd();
        // ......
    }
    

    ClassVisitorvisit()方法、visitField()方法和visitMethod()方法中都带有signature参数。这个signature参数与“泛型”密切相关;换句话说,如果处理的是一个带有泛型信息的类、字段或方法,那么就需要给signature参数提供一定的值;如果处理的类、字段或方法不带有“泛型”信息,那么将signature参数设置为null就可以了。

    如果大家对signature参数感兴趣,我们可以使用之前介绍的PrintASMCodeCore类去打印一下某个泛型类的ASM代码。例如,java.lang.Comparable是一个泛型接口,我们就可以使用PrintASMCodeCore类来打印一下它的ASM代码,从来查看signature参数的值是什么。

    方法的调用顺序

    ClassVisitor类当中,定义了多个visitXxx()方法。这些visitXxx()方法,遵循一定的调用顺序(可参考API文档):

    visit
    [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
    (
     visitAnnotation |
     visitTypeAnnotation |
     visitAttribute
    )*
    (
     visitNestMember |
     visitInnerClass |
     visitRecordComponent |
     visitField |
     visitMethod
    )* 
    visitEnd
    

    其中,涉及到一些符号,它们的含义如下:

    • []: 表示最多调用一次,可以不调用,但最多调用一次。
    • ()|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
    • *: 表示方法可以调用0次或多次。

    在本次课程当中,我们只关注ClassVisitor类当中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法这4个方法,所以上面的方法调用顺序可以简化如下:

    visit
    (
     visitField |
     visitMethod
    )* 
    visitEnd
    

    也就是说,先调用visit()方法,接着调用visitField()方法或visitMethod()方法,最后调用visitEnd()方法。

                    ┌─── visit()
                    │
                    ├─── visitField()
    ClassVisitor ───┤
                    ├─── visitMethod()
                    │
                    └─── visitEnd()
    

    visitXxx()方法与ClassFile

    visit()方法

    ClassVisitorvisitXxx()方法与ClassFile之间存在对应关系:

    ClassVisitor.visitXxx() --- .class --- ClassFile
    

    ClassVisitor中定义的visitXxx()方法,并不是凭空产生的,这些方法存在的目的就是为了生成一个合法的.class文件,而这个.class文件要符合ClassFile的结构,所以这些visitXxx()方法与ClassFile的结构密切相关。

    public void visit(
        final int version,
        final int access,
        final String name,
        final String signature,
        final String superName,
        final String[] interfaces);
    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];
    }
    
    ClassVisitor方法 参数 ClassFile
    ClassVisitor.visit() version minor_versionmajor_version
    access access_flags
    name this_class
    signature attributes的一部分信息
    superName super_class
    interfaces interfaces_countinterfaces
    ClassVisitor.visitField() field_info
    ClassVisitor.visitMethod() method_info

    visitField()方法

    public FieldVisitor visitField( // 访问字段
        final int access,
        final String name,
        final String descriptor,
        final String signature,
        final Object value);
    
    
    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    
    ClassVisitor方法 参数 field_info
    ClassVisitor.visitField() access access_flags
    name name_index
    descriptor descriptor_index
    signature attributes的一部分信息
    value attributes的一部分信息

    ClassWriter类

    ClassWriter的父类是ClassVisitor,因此ClassWriter类继承了visit()visitField()visitMethod()visitEnd()等方法。

    fields

    public class ClassWriter extends ClassVisitor {
        private int version;
        private final SymbolTable symbolTable;
    
        private int accessFlags;
        private int thisClass;
        private int superClass;
        private int interfaceCount;
        private int[] interfaces;
    
        private FieldWriter firstField;
        private FieldWriter lastField;
    
        private MethodWriter firstMethod;
        private MethodWriter lastMethod;
    
        private Attribute firstAttribute;
    
        //......
    }
    

    这些字段与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];
    }
    

    constructors

    第三个部分,就是ClassWriter定义的构造方法。

    ClassWriter定义的构造方法有两个,这里只关注其中一个,也就是只接收一个int类型参数的构造方法。在使用new关键字创建ClassWriter对象时,推荐使用COMPUTE_FRAMES参数。

    public class ClassWriter extends ClassVisitor {
        /* A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. */
        public static final int COMPUTE_MAXS = 1;
        /* A flag to automatically compute the stack map frames of methods from scratch. */
        public static final int COMPUTE_FRAMES = 2;
    
        // flags option can be used to modify the default behavior of this class.
        // Must be zero or more of COMPUTE_MAXS and COMPUTE_FRAMES.
        public ClassWriter(final int flags) {
            this(null, flags);
        }
    }
    

    小总结:

    • COMPUTE_MAXS: 计算max stack和max local信息。
    • COMPUTE_FRAMES: 既计算stack map frame信息,又计算max stack和max local信息。

    换句话说,COMPUTE_FRAMES是功能最强大的:

    COMPUTE_FRAMES = COMPUTE_MAXS + stack map frame
    

    methods

    visitXxx()方法

    ClassWriter这个类当中,我们仍然是只关注其中的visit()方法、visitField()方法、visitMethod()方法和visitEnd()方法。

    这些visitXxx()方法的调用,就是在为构建ClassFile提供“原材料”的过程。

    toByteArray()方法

    ClassWriter类当中,提供了一个toByteArray()方法。这个方法的作用是将“所有的努力”(对visitXxx()的调用)转换成byte[],而这些byte[]的内容就遵循ClassFile结构。

    toByteArray()方法的代码当中,通过三个步骤来得到byte[]

    • 第一步,计算size大小。这个size就是表示byte[]的最终的长度是多少。
    • 第二步,将数据填充到byte[]当中。
    • 第三步,将byte[]数据返回。

    如何使用ClassWriter类

    使用ClassWriter生成一个Class文件,可以大致分成三个步骤:

    • 第一步,创建ClassWriter对象。
    • 第二步,调用ClassWriter对象的visitXxx()方法。
    • 第三步,调用ClassWriter对象的toByteArray()方法。

    示例代码如下:

    import org.objectweb.asm.ClassWriter;
    
    import static org.objectweb.asm.Opcodes.*;
    
    public class HelloWorldGenerateCore {
        public static byte[] dump () throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit();
            cw.visitField();
            cw.visitMethod();
            cw.visitEnd();       // 注意,最后要调用visitEnd()方法
    
            // (3) 调用toByteArray()方法
            byte[] bytes = cw.toByteArray();
            return bytes;
        }
    }
    

    创建ClassWriter对象

    推荐使用COMPUTE_FRAMES

    在创建ClassWriter对象的时候,要指定一个flags参数,它可以选择的值有三个:

    • 第一个,可以选取的值是0。ASM不会自动计算max stacks和max locals,也不会自动计算stack map frames。
    • 第二个,可以选取的值是ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。
    • 第三个,可以选取的值是ClassWriter.COMPUTE_FRAMES(推荐使用)。ASM会自动计算max stacks和max locals,也会自动计算stack map frames。
    ┌──────────────────────┬─────────────────────────────────┬────────────────────────┐
    │        flags         │    max stacks and max locals    │    stack map frames    │
    ├──────────────────────┼─────────────────────────────────┼────────────────────────┤
    │          0           │               NO                │           NO           │
    ├──────────────────────┼─────────────────────────────────┼────────────────────────┤
    │     COMPUTE_MAXS     │               YES               │           NO           │
    ├──────────────────────┼─────────────────────────────────┼────────────────────────┤
    │    COMPUTE_FRAMES    │               YES               │          YES           │
    └──────────────────────┴─────────────────────────────────┴────────────────────────┘
    

    创建ClassWriter对象,如下所示:

    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    

    为什么推荐使用COMPUTE_FRAMES

    在创建ClassWriter对象的时候,使用ClassWriter.COMPUTE_FRAMES,ASM会自动计算max stacks和max locals,也会自动计算stack map frames。

    首先,来看一下max stacks和max locals。在ClassFile结构中,每一个方法都用method_info来表示,而方法里定义的代码则使用Code属性来表示,其结构如下:

    Code_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 max_stack;     // 这里是max stacks
        u2 max_locals;    // 这里是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];
    }
    

    如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_MAXSClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算Code结构中max_stackmax_locals的值。

    接着,来看一下stack map frames。在Code结构里,可能有多个attributes,其中一个可能就是StackMapTable_attributeStackMapTable_attribute结构,就是stack map frame具体存储格式,它的主要作用是对ByteCode进行类型检查。

    StackMapTable_attribute {
        u2              attribute_name_index;
        u4              attribute_length;
        u2              number_of_entries;
        stack_map_frame entries[number_of_entries];
    }
    

    如果我们在创建ClassWriter(flags)对象的时候,将flags参数设置为ClassWriter.COMPUTE_FRAMES,那么ASM会自动帮助我们计算StackMapTable_attribute的内容。

    image-20220408142122777

    我们推荐使用ClassWriter.COMPUTE_FRAMES。因为ClassWriter.COMPUTE_FRAMES这个选项,能够让ASM帮助我们自动计算max stacks、max locals和stack map frame的具体内容。

    • 如果将flags参数的取值为0,那么我们就必须要提供正确的max stacks、max locals和stack map frame的值;
    • 如果将flags参数的取值为ClassWriter.COMPUTE_MAXS,那么ASM会自动帮助我们计算max stacks和max locals,而我们则需要提供正确的stack map frame的值。

    那么,ASM为什么会提供0ClassWriter.COMPUTE_MAXS这两个选项呢?因为ASM在计算这些值的时候,要考虑各种各样不同的情况,所以它的算法相对来说就比较复杂,因而执行速度也会相对较慢。同时,ASM也鼓励开发者去研究更好的算法;如果开发者有更好的算法,就可以不去使用ClassWriter.COMPUTE_FRAMES,这样就能让程序的执行效率更高效。

    但是,不得不说,要想计算max stacks、max locals和stack map frames,也不是一件容易的事情。出于方便的目的,就推荐大家使用ClassWriter.COMPUTE_FRAMES。在大多数情况下,ClassWriter.COMPUTE_FRAMES都能帮我们计算出正确的值。在少数情况下,ClassWriter.COMPUTE_FRAMES也可能会出错,比如说,有些代码经过混淆(obfuscate)处理,它里面的stack map frame会变更非常复杂,使用ClassWriter.COMPUTE_FRAMES就会出现错误的情况。针对这种少数的情况,我们可以在不改变原有stack map frame的情况下,使用ClassWriter.COMPUTE_MAXS,让ASM只帮助我们计算max stacks和max locals。

    ClassWriter代码示例

    生成目标接口

    生成目标接口:MyHelloWord

    package sample;
    
    public interface MyHelloWord {
    }
    

    编码实现:

    public class HelloWorldDump implements Opcodes {
    
        public static void main(String[] args) throws Exception {
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(
                    V1_8,                                        // version
                    ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,   // access
                    "sample/MyHelloWord",                         // name
                    null,                                        // signature
                    "java/lang/Object",                          // superName
                    null                                         // interfaces
            );
    
            cw.visitEnd();
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    运行结果:

    image-20220408144741937

    在上述代码中,我们调用了visit()方法、visitEnd()方法和toByteArray()方法。

    由于sample.HelloWorld这个接口中,并没有定义任何的字段和方法,因此,在上述代码中没有调用visitField()方法和visitMethod()方法。

    visit()方法

    在这里,我们重点介绍一下visit(version, access, name, signature, superName, interfaces)方法的各个参数:

    • version: 表示当前类的版本信息。在上述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。
    • access: 表示当前类的访问标识(access flag)信息。在上面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。
    • name: 表示当前类的名字,它采用的格式是Internal Name的形式。
    • signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null
    • superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。
    • interfaces: 表示当前类实现了哪些接口信息。

    Internal Name:

    同时,我们也要介绍一下Internal Name的概念:在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。

    Java Language Java ClassFile
    文件格式 .java .class
    类名 Fully Qualified Class Name Internal Name
    类名示例 java.lang.String java/lang/String

    生成接口+字段+方法

    我们的预期目标:生成MyHelloWorld接口。

    public interface MyHelloWorld extends Cloneable {
        int LESS = -1;
        int EQUAL = 0;
        int GREATER = 1;
        int compareTo(Object o);
    }
    

    编码实现:

    public class HelloWorldGenerateCore implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
    
            FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "sample/MyHelloWord",
                    null, "java/lang/Object", new String[]{"java/lang/Cloneable"});
            {
                FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, -1);
                fv1.visitEnd();
            }
            {
                FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, 0);
                fv2.visitEnd();
            }
            {
                FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, 1);
                fv3.visitEnd();
            }
            {
                MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null);
                mv1.visitEnd();
            }
            cw.visitEnd();
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    运行效果:

    image-20220408153550394

    visitField()和visitMethod()方法

    在这里,我们重点说一下visitField()方法和visitMethod()方法的各个参数:

    • visitField (access, name, descriptor, signature, value)
    • visitMethod(access, name, descriptor, signature, exceptions)

    这两个方法的前4个参数是相同的,不同的地方只在于第5个参数。

    • access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLICACC_STATICACC_FINAL等。
    • name参数:表示当前字段或方法的名字。
    • descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。
    • signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
    • value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。
    • exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法头(Method Header)中是否具有throws XxxException相关。

    我们可以使用PrintASMCodeCore类来查看下面的sample.HelloWorld类的ASM代码,从而观察value参数和exceptions参数的取值:

    我们可以使用PrintASMCodeCore类来查看下面的sample.HelloWorld类的ASM代码,从而观察value参数和exceptions参数的取值:

    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class HelloWorld {
        // 这是一个常量字段,使用static、final关键字修饰
        public static final int constant_field = 10;
        // 这是一个非常量字段
        public int non_constant_field;
    
        public void test() throws FileNotFoundException, IOException {
            // do nothing
        }
    }
    

    对于上面的代码,

    • constant_field字段:对应于visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "constant_field", "I", null, new Integer(10))
    • non_constant_field字段:对应于visitField(ACC_PUBLIC, "non_constant_field", "I", null, null)
    • test()方法:对应于visitMethod(ACC_PUBLIC, "test", "()V", null, new String[] { "java/io/FileNotFoundException", "java/io/IOException" })

    描述符(descriptor)

    在ClassFile当中,描述符(descriptor)是对“类型”的简单化描述。

    • 对于字段(field)来说,描述符(descriptor)就是对字段本身的类型进行简单化描述。
    • 对于方法(method)来说,描述符(descriptor)就是对方法的接收参数的类型返回值的类型进行简单化描述。
    Java类型 ClassFile描述符
    boolean Z(Z表示Zero,零表示false,非零表示true
    byte B
    char C
    double D
    float F
    int I
    long J
    short S
    void V
    non-array reference L<InternalName>;
    array reference [

    对字段描述符的举例:

    • boolean flag: Z
    • byte byteValue: B
    • int intValue: I
    • float floatValue: F
    • double doubleValue: D
    • String strValue: Ljava/lang/String;
    • Object objValue: Ljava/lang/Object;
    • byte[] bytes: [B
    • String[] array: [Ljava/lang/String;
    • Object[][] twoDimArray: [[Ljava/lang/Object;

    对方法描述符的举例:

    • int add(int a, int b): (II)I
    • void test(int a, int b): (II)V
    • boolean compare(Object obj): (Ljava/lang/Object;)Z
    • void main(String[] args): ([Ljava/lang/String;)V

    生成类

    我们的预期目标:生成HelloWorld类。

    public class MyHelloWorld {
    }
    

    编码实现:

    public class HelloWorldGenerateCoreClass implements Opcodes {
    
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/MyHelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/MyHelloWord",
                    null, "java/lang/Object", null);
    
            //添加无参构造方法
            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
            cw.visitEnd();
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    
    }
    

    运行结果:

    image-20220408161446342

    <init>()和<clinit>()方法

    对于一个类(Class)来说,如果没有提供任何构造方法,Java编译器会自动生成一个默认构造方法。在所有的.class文件中,构造方法的名字是<init>()

    另外,如果在.class文件中包含静态代码块,那么就会有一个<clinit>()方法。

    package sample;
    
    public class HelloWorld {
        static {
            System.out.println("static code block");
        }
    }
    

    上面的静态代码码,对应于visitMethod(ACC_STATIC, "<clinit>", "()V", null, null)的调用。

    总结

    • 第一点,我们需要注意ClassWriter/ClassVisitorvisit()visitField()visitMethod()visitEnd()方法的调用顺序。
    • 第二点,我们对于visit()方法、visitField()方法和visitMethod()方法接收的参数进行了介绍。虽然我们并没有特别介绍visitEnd()方法和toByteArray()方法,并不表示这两个方法不重要,只是因为这两个方法不接收任何参数。
    • 第三点,我们介绍了Internal Name和Descriptor(描述符)这两个概念,在使用时候需要加以注意,因为它们与我们在使用Java语言编写代码时是不一样的。
    • 第四点,在.class文件中,构造方法的名字是<init>(),表示instance initialization method;静态代码块的名字是<clinit>(),表示class initialization method。
  • 相关阅读:
    info plist各个功能的详解
    打个测试包弄成连接供别人下载测试
    封装的数据请求加上风火轮的效果
    福大软工 · BETA 版冲刺前准备(团队)
    福大软工 · 第十一次作业
    Alpha 冲刺 (10/10)
    Alpha 冲刺 (9/10)
    Alpha 冲刺 (8/10)
    Alpha 冲刺 (7/10)
    Alpha 冲刺 (6/10)
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/16118083.html
Copyright © 2020-2023  润新知