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;
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, "", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        cw.visitEnd();
        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }

}

运行结果:

image-20220408161446342

()和()方法

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

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

package sample;

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

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

总结

  • 第一点,我们需要注意ClassWriter/ClassVisitorvisit()visitField()visitMethod()visitEnd()方法的调用顺序。
  • 第二点,我们对于visit()方法、visitField()方法和visitMethod()方法接收的参数进行了介绍。虽然我们并没有特别介绍visitEnd()方法和toByteArray()方法,并不表示这两个方法不重要,只是因为这两个方法不接收任何参数。
  • 第三点,我们介绍了Internal Name和Descriptor(描述符)这两个概念,在使用时候需要加以注意,因为它们与我们在使用Java语言编写代码时是不一样的。
  • 第四点,在.class文件中,构造方法的名字是(),表示instance initialization method;静态代码块的名字是(),表示class initialization method。
asm