原文:https://lsieun.github.io/java-asm-01/class-visitor-intro.html
ClassVisitor类
在ASM Core API中,最重要的三个类就是ClassReader
、ClassVisitor
和ClassWriter
类。在进行Class Generation操作的时候,ClassVisitor
和ClassWriter
这两个类起着重要作用,而并不需要ClassReader
类的参与。在本文当中,我们将对ClassVisitor
类进行介绍。
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
串连起来。
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();
// ......
}
在ClassVisitor
的visit()
方法、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()方法
ClassVisitor
的visitXxx()
方法与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_version 和major_version |
access |
access_flags |
|
name |
this_class |
|
signature |
attributes 的一部分信息 |
|
superName |
super_class |
|
interfaces |
interfaces_count 和interfaces |
|
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_MAXS
或ClassWriter.COMPUTE_FRAMES
,那么ASM会自动帮助我们计算Code
结构中max_stack
和max_locals
的值。
接着,来看一下stack map frames。在Code
结构里,可能有多个attributes
,其中一个可能就是StackMapTable_attribute
。StackMapTable_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
的内容。
我们推荐使用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为什么会提供0
和ClassWriter.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();
}
}
运行结果:
在上述代码中,我们调用了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();
}
}
运行效果:
visitField()和visitMethod()方法
在这里,我们重点说一下visitField()
方法和visitMethod()
方法的各个参数:
visitField (access, name, descriptor, signature, value)
visitMethod(access, name, descriptor, signature, exceptions)
这两个方法的前4个参数是相同的,不同的地方只在于第5个参数。
access
参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC
、ACC_STATIC
和ACC_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();
}
}
运行结果:
<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
/ClassVisitor
中visit()
、visitField()
、visitMethod()
和visitEnd()
方法的调用顺序。 - 第二点,我们对于
visit()
方法、visitField()
方法和visitMethod()
方法接收的参数进行了介绍。虽然我们并没有特别介绍visitEnd()
方法和toByteArray()
方法,并不表示这两个方法不重要,只是因为这两个方法不接收任何参数。 - 第三点,我们介绍了Internal Name和Descriptor(描述符)这两个概念,在使用时候需要加以注意,因为它们与我们在使用Java语言编写代码时是不一样的。
- 第四点,在
.class
文件中,构造方法的名字是<init>()
,表示instance initialization method;静态代码块的名字是<clinit>()
,表示class initialization method。