Java字节码(.class文件)格式详解
小介:去 年在读《深入解析JVM》的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分。现在都不知道还有没有保留着,貌似Apache有现 成的BCEL工程可以做这件事。当时也只是为了学习。这份资料主要参考《深入解析JVM》和《Java虚拟机规范》貌似是1.2版本的,整理出来的。里面 包含了一些自己的理解和用实际代码的测试。有兴趣的童鞋可以研究研究。嘿嘿。要有错误也希望能为小弟指点出来,感激不尽。:)
1.总体格式
Class File format | ||
type | descriptor | remark |
u4 | magic | 0xCAFEBABE |
u2 | minor_version | |
u2 | major_version | |
u2 | constant_pool_count | |
cp_info | constant_pool[cosntant_pool_count – 1] | index 0 is invalid |
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] |
2. 格式详解
2.1 magic
magic被称为“魔数”,用来标识.class文件的开头。所有合法的.class字节码都应该是该数开头,占4个字节。
2.2 major_version.minor_version
major_version.minor_version合在一起形成当前.class文件的版本号,该版本号一般由编译器产生,并且由sun定义。如59.0。它们一起占4个字节。
2.3 constant_pool
在Java字节码中,有一个常量池,用来存放不同类型的常量。由于Java设计的目的之一就是字节码需要经网络传输的,因而字节码需要比较紧凑,以减少网络传输的流量和时间。常量池的存在则可以让一些相同类型的值通过索引的方式从常量池中找到,而不是在不同地方有不同拷贝,缩减了字节码的大小。
每个常量池中的项是通过cp_info的类型来表示的,它的格式如下:
cp_info format | ||
type | descriptor | remark |
u1 | tag | |
u1 | info[] |
这里tag用来表示当前常量池不同类型的项。info中存放常量池项中存放的数据。
tag中表示的数据类型:
CONSTANT_Class_info (7)、
CONSTANT_Integer_info (3)、
CONSTANT_Long_info (5)、
CONSTANT_Float_info (4)、
CONSTANT_Double_info (6)、
CONSTANT_String_info (8)、
CONSTANT_Fieldref_info (9)、
CONSTANT_Methodref_info (10)、
CONSTANT_InterfaceMethodref_info (11)、
CONSTANT_NameAndType_info (12)、
CONSTANT_Utf8_info (1)、
注:在Java字节码中,所有boolean、byte、char、short类型都是用int类型存放,因而在常量池中没有和它们对应的项。
2.3.1 CONSTANT_Class_info
用于记录类或接口名(used to represent a class or an interface)
CONSTANT_Class_info format | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Class (7) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。 |
注:在Java字节码中,类和接口名不同于源码中的名字,详见附件A.
2.3.2 CONSTANT_Integer_info
用于记录int类型的常量值(represent 4-byte numeric (int) constants:)
CONSTANT_Integer_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Integer (3) |
u4 | bytes | 整型常量值 |
2.3.3 CONSTANT_Long_info
用于记录long类型的常量值(represent 8-byte numeric (long) constants:)
CONSTANT_Long_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Long (5) |
u4 | high_bytes | 长整型的高四位值 |
u4 | low_bytes | 长整型的低四位值 |
2.3.4 CONSTANT_Float_info
用于记录float类型的常量值(represent 4-byte numeric (float) constants:)
CONSTANT_Float_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Float(4) |
u4 | bytes | 单精度浮点型常量值 |
几个特殊值:0x7f800000 => Float.POSITIVE_INFINITY、0xff800000 => Float.NEGATIVE_INFINITY、
0x7f800001 to 0x7fffffff => Float.NaN、0xff800001 to 0xffffffff => Float.NaN
2.3.5 CONSTANT_Double_info
用于记录double类型的常量值(represent 8-byte numeric (double) constants:)
CONSTANT_Double_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Double(6) |
u4 | high_bytes | 双精度浮点的高四位值 |
u4 | low_bytes | 双精度浮点的低四位值 |
几个特殊值:0x7ff0000000000000L => Double.POSITIVE_INFINITY、
0xfff0000000000000L => Double.NEGATIVE_INFINITY
0x7ff0000000000001L to 0x7fffffffffffffffL => Double.NaN 、
0xfff0000000000001L to 0xffffffffffffffffL => Double.NaN
2.3.6 CONSTANT_String_info
用于记录常量字符串的值(represent constant objects of the type String:)
CONSTANT_String_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_String(8) |
u2 | string_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。 |
2.3.7 CONSTANT_Fieldref_info
用于记录字段信息(包括类或接口中定义的字段以及代码中使用到的字段)。
CONSTANT_Fieldref_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Fieldref(9) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。 |
2.3.8 CONSTANT_Methodref_info
用于记录方法信息(包括类中定义的方法以及代码中使用到的方法)。
CONSTANT_Methodref_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Methodref(10) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。 |
2.3.9 CONSTANT_InterfaceMethodref_info
用于记录接口中的方法信息(包括接口中定义的方法以及代码中使用到的方法)。
CONSTANT_InterfaceMethodref_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_InterfaceMethodref(11) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。 |
2.3.10 CONSTANT_NameAndType_info
记录方法或字段的名称(name)和描述符(descriptor)(represent a field or method, without indicating which class or interface type it belongs to:)。
CONSTANT_NameAndType_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_NameAndType (12) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_utf8_info类型。指定字段或方法的描述符(见附录C) |
2.3.11 CONSTANT_Utf8_info
记录字符串的值(represent constant string values. String content is encoded in modified UTF-8.)
modifie
d UTF-8 refer to :
cle.com/javase/1.4.2/docs/api/java/io/DataInputStream.html
CONSTANT_Utf8_info | ||
type | descriptor | remark |
u1 | tag | CONSTANT_Utf8 (1) |
u2 | length | bytes所代表
的字符串的长度 |
u1 | bytes[length] | 字符串的byte数据,可以通过DataInputStream中的readUtf()方法(实例方法或静态方法读取该二进制的字符串的值。) |
2.4 access_flags
指定类或接口的访问权限。
类或接口的访问权限 | ||
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,包外可访问。 |
ACC_FINAL | 0x0010 | final,不能有子类。 |
ACC_SUPER | 0x0020 | 用于兼容早期编译器,新编译器都设置该标记,以在使用 invokespecial指令时对子类方法做特定处理。 |
ACC_INTERFACE | 0x0200 | 接口,同时需要设置:ACC_ABSTRACT。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM |
ACC_ABSTRACT | 0x0400 | 抽象类,无法实例化。不可和ACC_FINAL同时设置。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
ACC_ANNOTATION | 0x2000 | 注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT |
ACC_ENUM | 0x4000 | 枚举类型 |
2.5 this_class
this_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口。
2.6 super_class
super_class是指向constant pool的索引值,该值必须是CONSTANT_Class_info类型,指定当前字节码定义的类或接口的直接父类。只有Object类才没有直接父类,此时该索引值为0。并且父类不能是final类型。接口的父类都是Object类。
2.7 interfaces
interfaces数组记录所有当前类或接口直接实现的接口。interfaces数组中的每项值都是一个指向constant pool的索引值,这些值必须是CONSTANT_Class_info类型。数组中接口的顺序和源代码中接口定义的顺序相同。
2.8 fields
fields数组记录了类或接口中的所有字段,包括实例字段和静态字段,但不包含父类或父接口中定义的字段。fields数组中每项都是field_info类型值,它描述了字段的详细信息,如名称、描述符、字段中的attribute等。
field_info | ||
type | descriptor | remark |
u2 | access_flags | 记录字段的访问权限。见2.8.1 |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段的名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info类型,指定字段的描述符(见附录C)。 |
u2 | attributes_count | attributes包含的项目数。 |
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。见2.8.2-2.8.7 |
注:fields中的项目和CONSTANT_Fieldref_info中的项目部分信息是相同的,他们主要的区别是CONSTANT_Fieldref_info中的项目不仅包含了类或接口中定义的字段,还包括在字节码中使用到的字段信息。不过这里很奇怪,为什么field_info结构中不把name_index和descriptor_index合并成fieldref_index,这样的class文件不是更加紧凑吗??不知道这是sun因为某些原因故意这样设计还是这是他们的失误??
2.8.1 字段访问权限
字段的访问权限 | ||
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,包外可访问。 |
ACC_PRIVATE | 0x0002 | private,只可在类内访问。 |
ACC_PROTECTED | 0x0004 | protected,类内和子类中可访问。 |
ACC_STATIC | 0x0008 | static,静态。 |
ACC_FINAL | 0x0010 | final,常量。 |
ACC_VOILATIE | 0x0040 | volatile,直接读写内存,不可被缓存。不可和ACC_FINAL一起使用。 |
ACC_TRANSIENT | 0x0080 | transient,在序列化中被忽略的字段。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
ACC_ENUM | 0x4000 | enum,枚举类型字段 |
注:接口中的字段必须同时设置:ACC_PUBLIC、ACC_STATIC、ACC_FINAL
2.8.2 ConstantValue Attribute (JVM识别)
ConstantValue Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“ConstantValue”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(固定值:2) |
u2 | constant_value_index | constant_pool中的索引,
CONSTANT_Integer_info(int,boolean,char、short、byte)、 CONSTANT_Float_info(float)、 Constant_Double_info(double)、 CONSTANT_Long_info(long) CONSTANT_String_info(String)类型 |
每个常量字段(final,静态常量或实例常量)都包含有且仅有一个ConstantValue Attribute。ConstantValue Attribute结构用于存储一个字段的常量值。
对一个静态常量字段,该常量值会在类或接口被初始化之前,由JVM负责赋给他们,即它在任何静态字段之前被赋值。
对一个非静态常量字段,该值会被虚拟机忽略,它的赋值由生成的实例初始化函数(<init>)实现。如类:
class A {
public static final int fa = 10;
public final int fa2 = 30;
private static int sa = 20;
static {
sa = 30;
}
}
生成的字节码如下:
// Compiled from Test.java (version 1.6 : 50.0, super bit)
class org.levin.insidejvm.miscs.staticinit.A {
public static final int fa = 10;
public final int fa2 = 30;
private static int sa;
static {};
0 bipush 20
2 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
5 bipush 30
7 putstatic org.levin.insidejvm.miscs.staticinit.A.sa : int [16]
10 return
public A();
0 aload_0 [this]
1 invokespecial java.lang.Object() [21]
4 aload_0 [this]
5 bipush 30
7 putfield org.levin.insidejvm.miscs.staticinit.A.fa2 : int [23]
10 return
2.8.3 Synthetic Attribute
参考2.11.1
2.8.4 Signature Attribute
参考2.11.2
2.8.5 Deprecated Attribute
参考2.11.3
2.8.6 RuntimeVisibleAnnotations Attribute
参考2.11.4
2.8.7 RuntimeInvisibleAnnotations Attribute
参考2.11.5
2.9 methods
methods数组记录了类或接口中的所有方法,包括实例方法、静态方法、实例初始化方法和类初始化方法,但不包括父类或父接口中定义的方法。methods数组中每项都是method_info类型值,它描述了方法的详细信息,如名称、描述符、方法中的attribute(如Code Attribute记录了方法的字节码)等。
method_info | ||
type | descriptor | remark |
u2 | access_flags | 记录方法的访问权限。见2.9.1 |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定方法名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info类型,指定方法的描述符(见附录C)。 |
u2 | attributes_count | attributes包含的项目数。 |
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。见2.9.2-2.9.11 |
注:methods数组同样有和fields数组一样的问题,包括fields中项和CONSTANT_Methodref_info以及CONSTANT_InterfaceMethodref_info中的区别。以及设计上的问题。详见field_info中的注。
2.9.1 方法访问权限
方法的访问权限 | ||
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,包外可访问。 |
ACC_PRIVATE | 0x0002 | private,只可在类内访问。 |
ACC_PROTECTED | 0x0004 | protected,类内和子类中可访问。 |
ACC_STATIC | 0x0008 | static,静态。 |
ACC_FINAL | 0x0010 | final,不可被重写。 |
ACC_SYNCHRONIZED | 0x0020 | synchronized,同步方法。 |
ACC_BRIDGE | 0x0040 | bridge方法,由编译器生成。(什么是bridge方法?) |
ACC_VARARGS | 0x0080 | 包含不定参数个数的方法。 |
ACC_NATIVE | 0x0100 | native,非Java语言实现的方法。(如何实现Native方法?) |
ACC_ABSTRACT | 0x0400 | abstract,抽象方法。 |
ACC_STRICT | 0x0800 | strictfp,设置floating-point模式为FP-strict。(什么是FP-strict模式?) |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
注:接口中的方法必须同时设置:ACC_PUBLIC、ACC_ABSTRACT。设置了ACC_ABSTRACT后,不可以再设置ACC_FINAL、ACC_STATIC、ACC_PRIVATE、ACC_NATIVE、ACC_SYNCHRONIZED 、ACC_STRICT。
2.9.2 Code Attribute (JVM识别)
每个非abstract、非native方法的attributes集合都包含有且仅有一项Code Attribute。它包含了一个方法的栈、局部变量、字节码以及和代码相关的Attribute信息。
Code Attribute | ||||||||||||||||||||||
type | descriptor | remark | ||||||||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“Code”)。 | ||||||||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | ||||||||||||||||||||
u2 | max_stack | 该方法操作栈的最大深度。 | ||||||||||||||||||||
u2 | max_locals | 该方法调用时需要分配的局部变量的最大个数,包括该方法的参数。 | ||||||||||||||||||||
u4 | code_length | 该方法字节码长度(以字节为单位) | ||||||||||||||||||||
u1 | code[code_length] | 存放字节码数组(字节码如何解析?)。 | ||||||||||||||||||||
u2 | exception_table_length | 异常表的长度。 | ||||||||||||||||||||
|
||||||||||||||||||||||
u2 | attributes_count | attributes包含的项目数。 | ||||||||||||||||||||
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。见2.9.2.1-2.9.2.4 |
2.9.2.1 StackMapTable Attribute (JVM识别)
StackMapTable Attribute在J2SE 6中引入,记录了类型检查时需要用到的信息,如字节码的偏移量、局部变量的验证类型、操作栈中的验证类型,用于类型检查过程。在Code Attribute只能包含一项StackMapTable Attribute,记录所有当前Code Attribute中的验证信息。
一项StackMapTable Attribute中包含多项stack_map_frame。每项stack_map_frame显式或隐式得记录了字节码的偏移量、局部变量验证类型和操作栈的验证类型。验证器就是通过获取局部变量类型和操作栈类型进行验证的(具体如何验证呢?)。
在stack_map_frame中,并不是直接记录了字节码的索引值,而是记录了offset_delta的值。stack_map_frame中的每一项都通过前一项的值+1+offset_delta计算出当前项对应的真正的字节码的位置,只有当当前stack_map_frame的前一项是当前方法的初始帧(initial frame of the method,什么是方法的初始帧?)的时候,offset_delta的值才直接表示字节码位置。
(为什么不直接记录字节码的索引值?为了保证stack_map_frame被正确的排序了。为什么要加1再加offset_delta?为了避免重复出现stack_map_frame项。)
StackMapTable Attribute | ||||||||||||||||||||||
type | descriptor | remark | ||||||||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“StackMapTable”)。 | ||||||||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | ||||||||||||||||||||
u2 | number_of_entries | stack_map_frame项的数目。 | ||||||||||||||||||||
|
verification_type_info(union,联合体类型) | |
记录字节码的验证类型。每一项第一个字节指定验证类型(tag)。 | |
Top_variable_info | Top_variable_info {
u1 tag = ITEM_Top; /* 0 */ } 指定局部变量的验证类型为Top。 |
Integer_variable_info | Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */ } 指定验证类型为int。 |
Float_variable_info | Float_variable_info {
u1 tag = ITEM_Float; /* 2 */ } 指定验证类型为float。 |
Long_variable_info | Long_variable_info {
u1 tag = ITEM_Long; /* 4 */ } 指定验证类型为long。 |
Double_variable_info | Double_variable_info {
u1 tag = ITEM_Double; /* 3 */ } 指定验证类型为double。 |
Null_variable_info | Null_variable_info {
u1 tag = ITEM_Null; /* 5 */ } 指定验证类型为null。 |
UninitializedThis_variable_info | UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */ } 指定验证类型为uninitializedThis(什么是uninitializedThis?)。 |
Object_variable_info | Object_variable_info {
u1 tag = ITEM_Object; /* 7 */ u2 cpool_index; //constant_pool索引,CONSTANT_Class_info类型。 } 指定验证类型为cpool_index中指定的类型实例。 |
Uninitialized_variable_info | Uninitiated_variable_info {
u1 tag = ITEM_Uninitialized; /* 8 */ u2 offset; } 指定验证类型为uninitialized(这种类型是指什么?)。offset记录了用于创建实例的new指令的偏移量。(The offset item indicates the offset of the new instruction that created the object being stored in the location.这段话是什么意思?) |
注:对验证过程不太了解,因而StackMapTable Attribute的一些描述也没能理解。
2.9.2.2 LineNumberTable Attribute (调试信息)
LineNumberTable Attribute用于调试器,以获取某条指令对应的源代码中的行号。多条指令可以对应相同的行号。
LineNumberTable Attribute | |||||||||||||||||
type | descriptor | remark | |||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LineNumberTable”)。 | |||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||
u2 | line_number_table_length | 异常表的长度。 | |||||||||||||||
|
2.9.2.3 LocalVariableTable Attribute (调试信息)
LocalVariableTable Attribute用于调试器,以获取在方法运行时局部变量的信息。在一个Code Attribute中只包含1或0项LocalVariableTable Attribute。
LocalVariableTable Attribute | |||||||||||||||||||||||||
type | descriptor | remark | |||||||||||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LocalVariableTable”)。 | |||||||||||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||||||||||
u2 | local_variable_table_length | 局部变量表的长度。 | |||||||||||||||||||||||
|
2.9.2.4 LocalVariableTypeTable Attribute (调试信息)
LocalVariableTypeTable Attribute用于调试器,以获取在方法运行时泛型局部变量的信息。在一个Code Attribute中只包含1或0项LocalVariableTypeTable Attribute。
LocalVariableTable Attribute和LocalVariableTypeTable Attribute表达的信息是类似的,他们的区别是对泛型类型的局部变量,需要用Signature的形式表达,而不能仅仅用Descriptor的形式表达,因而对泛型类型的局部变量,需要在LocalVariableTable Attribute和LocalVariableTypeTable Attribute中同时存在一项;而对非泛型类型的局部变量来说,只要在LocalVariableTable Attribute存在表项就可以了。
从这里我们也可以看出泛型是后期才被字节码所支持的痕迹。我感觉很奇怪的是Java在设计的时候,泛型应该已经开始流行了,为什么它在设计之初没有把它考虑进去,而要到后期加入,然后让这种修补的设计做的那么糟糕呢,Java的设计者如果能在设计的时候把它作为扩展考虑,然后再后期去实现,不是更好吗?
LocalVariableTypeTable Attribute | |||||||||||||||||||||||||
type | descriptor | remark | |||||||||||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LocalVariableTypeTable”)。 | |||||||||||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||||||||||
u2 | local_variable_type_table_length | 泛型局部变量表的长度。 | |||||||||||||||||||||||
|
2.9.3 Exceptions Attribute (JVM识别)
Exceptions Attribute记录了一个方法需要检验的异常类型。一个method_info的attributes中只能包含一项Exceptions Attribute。即记录一个方法可以抛出的异常类型。
一个方法可以抛出的异常类型遵循三点:
1. 抛出的异常是RuntimeException类型或其子类。
2. 抛出的异常是Error类型或其子类。
3. 抛出的异常是Exceptions Attribute中记录的类型或它们的子类。
Exceptions Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“Exceptions”)。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
u2 | number_of_exceptions | exception_index_table表项长度 |
u2 | exception_index_table[number_of_exceptions] | 每项为constant_pool中的索引,CONSTANT_Class_info类型。记录该方法可抛出的异常类型。 |
2.9.4 RuntimeVisibleParameterAnnotations Attribute
RuntimeVisibleParameterAnnotations Attribute记录该方法在运行时可见的修饰该方法参数的Annotation,从而Java程序可以通过反射机制获取这些Annotation中的值。一个method_info中的attributes中只能包含一项RuntimeVisibleAnnotations Attribute。
RuntimeVisibleAnnotations Attribute | |||||||||||||||||
type | descriptor | remark | |||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“RuntimeVisibleAnnotations”)。 | |||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||
u2 | num_parameters | 记录该方法中参数个数 | |||||||||||||||
|
注:从该数据结构的定义中可以看到,Java对Parameter Annotation存放的信息是很少的,我们只能依赖于定义的顺序来获取这些Annotation,而不能通过参数名或者参数类型来获取相应的Annotation。事实上,
1. 由于参数名的信息只在调试时才有,如LocalVariableTable Attribute或LocalVariableTypeTable Attribute中;
2. 而一个方法中不同参数的类型极有可能是相同的;
因而从逻辑上来说,通过参数名或者参数类型返回相应的Annotation信息的方式也是不合理的。由于这个原因,在java.lang.reflect.Method类的方法中也只是给出了:
Annotation[][] getParameterAnnotations()
的方法,获得所有参数中的Annotation,这里的二维数组一维代表参数,一维代表多个Annotation,它们的顺序和源码定义时顺序相同。
2.9.5 RuntimeInvisibleParameterAnnotations Attribute
RuntimeInvisibleParameterAnnotations Attribute记录该方法在运行时不可见的修饰该方法参数的Annotation。一个method_info中的attributes中只能包含一项RuntimeInvisibleAnnotations Attribute。
RuntimeInvisibleParameterAnotations Attribute和RuntimeVisibleAnnotations Attribute的区别在于:
后者中的Annotation默认情况下,可以通过Java提供的反射函数获取相应的Annotation,而前者的Annotation在默认情况下是无法通过Java提供的反射函数被获取的,而需要通过特定的机制(如设置JVM的特定参数,该机制由不同的JVM实现来决定)才能通过Java提供的反射函数获取内部的Annotation。
然而这样就又有一个问题了,RuntimeInvisibleParameterAnotations Attribute是如何被填入值的呢?通过什么机制让源码中方法参数的Annotation是默认不可见的呢?我感觉这个也可能也是由不同编译器提供不同的机制来实现的,不知道sun提供的编译器有没有什么机制支持它了??
RuntimeInvisibleAnnotations Attribute | |||||||||||||||||
type | descriptor | remark | |||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“RuntimeInvisibleAnnotations”)。 | |||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||
u2 | num_parameters | 记录该方法中参数个数 | |||||||||||||||
|
2.9.6 AnnotationDefault Attribute
AnnotationDefault Attribute用于Annotation类型方法中,以记录该方法所代表的Annotation类型的默认值。每个Annotation类型的method_info中的attributes中只能包含一个AnnotationDefault Attribute项。如:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Test {
public int id() default -1;
public String description() default “no description”;
}
该Annotation类产生的class二进制文件中的id方法和description方法的attributes数组中都会包含一项AnnotationDefault Attribute,它们的默认值分别为-1(CONSTANT_Integer_info类型)和”no description”(CONSTANT_String_info类型)。
AnnotationDefault Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“AnnotationDefault”)。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
element_value | default_value | 记录该方法表示的Annotation类型的默认值。(element_value结构详见附录E) |
2.9.7 Synthetic Attribute
参见2.11.1
2.9.8 Signature Attribute
参见2.11.2
2.9.9 Deprecated Attribute
参见2.11.3
2.9.10 RuntimeVisibleAnnotations Attribute
参见2.11.4
2.9.11 RuntimeInvisibleAnnotations Attribute
参见2.11.5
2.10 attributes
attributes数组记录了和类或接口相关的所有Attribute项(和字段相关的Attribute在field_info的attributes中,和方法相关的Attribute在method_info的attrubutes中,和字节码相关的Attribute在Code Attribute的attributes中)。attributes数组中的每项都是attribute_info类型,它描述了Attribute的名称、详细信息等。该attributes数组描述了ClassFile的一些额外信息。JVM必须忽略它不能识别的Attribute,而且那些JVM不能识别的的Attribute也不能影响class文件的语义。
当前定义的Attribute有:Code Attribute、Constant Value Attibute、Deprecated Attribute、Enclosing Method Attribute、Exceptions Attribute、Inner Classes Attribute、Line Number Table Attribute、Local Variable Table Attribute、Local Variable Type Table Attribute、Runtime Visible Annotations Attribute、Runtime Invisible Annotation Attribute、Runtime Visible Parameter Annotation Attribute、Runtime Invisible Parameter Annotation Attribute、Signature Attribute、Source Debug Extension Attribute、Source File Attribute、Stack Map Table Attribute、Synthetic Attribute、Annotation Default Attribute等。它们有些只存在于field_info中,有些只存在method_info中,有些只存在ClassFile中,有些只存在于Code Attribute中,还有些可以同时存在于field_info、method_info、classfile中。
Attribute结构只存在与ClassFile、method_info、field_info、Code Attribute结构中。
attribute_info(Attribute的基本数据结构) | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
u1 | info[attribute_length] | 记录Attribute的内容字节数据。 |
在用户自定义的编译器或者Java虚拟机中可以扩展ClassFile中的Attribute表(即自定义新的Attribute)。但是对自定义的Attribute必须遵循一些规则:
1. 用户自定义的新的Attribute(非sun定义的Attribute)命名必须是遵循Java命名规则,即加入公司的包名信息,如:“com.levin.new_attribute”
2. 新增的Attribute只可以作为辅助的信息,如增加和自定义调试器相关的调试信息,但是它们不可以改变ClassFile的语义。什么叫改变ClassFile的语义呢?我现在的理解,比如在自定义的Java虚拟机中,为某个方法新增一个Attribute,用以标记该方法在运行是不可以被调用。不知道这个例子合适不合适。
3. 对于自定义的Java虚拟机,禁止因为某些它不识别的Attribute存在而抛出异常或者直接报错。但是这一层限制可以加载自定义编译器中。Java虚拟机必须忽略它不识别的Attribute。
以下是定义在ClassFile中的Attribute。
2.10.1 InnerClasses Attribute
InnerClasses记录当前类的所有内部类。当前类需要记录的内部类的算法如下:
1. 当前类中定义的内部类,包括方法中定义的类。
2. 如果当前类本身是内部类,则还要记录当前类的外部类,直到外部类不是一个内部类。
如:
class Outer {
public class Inner {
public void getMethod() {
class Inner3 {
class Inner4 {
}
}
}
public class Inner2 {
public class Inner5 {
}
}
}
public class Inner_1 {
}
}
Outer中的InnerClasses Attribute:
[inner class info: #17 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer
inner name: #19 Inner, accessflags: 1 public],
[inner class info: #20 org/levin/insidejvm/miscs/instructions/Outer$Inner_1, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer
inner name: #22 Inner_1, accessflags: 1 public]
Inner中的InnerClasses Attribute:
[inner class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #23 org/levin/insidejvm/miscs/instructions/Outer
inner name: #25 Inner, accessflags: 1 public],
[inner class info: #26 org/levin/insidejvm/miscs/instructions/Outer$Inner$1Inner3, outer class info: #0
inner name: #28 Inner3, accessflags: 16 final],
[inner class info: #29 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner
inner name: #31 Inner2, accessflags: 1 public]
Inner5中的InnerClasses Attribute:
[inner class info: #22 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #24 org/levin/insidejvm/miscs/instructions/Outer
inner name: #26 Inner, accessflags: 1 public],
[inner class info: #27 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2, outer class info: #22 org/levin/insidejvm/miscs/instructions/Outer$Inner
inner name: #29 Inner2, accessflags: 1 public],
[inner class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2$Inner5, outer class info: #27 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2
inner name: #30 Inner5, accessflags: 1 public]
InnerClasses Attribute | |||||||||||||||||||||||
type | descriptor | remark | |||||||||||||||||||||
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“InnerClasses”)。 | |||||||||||||||||||||
u4 | attribute_length | 该Attribute内容的字节长度。 | |||||||||||||||||||||
u2 | number_of_classes | 记录内部类的数量。 | |||||||||||||||||||||
|
注:为什么需要为内部类保留那么多的信息呢?是为了在反射的时候获取必要的信息或者在反编译的时候可以更好的还原源代码的结构吗?还是有其他的作用?
内部类的访问权限 | ||
Flag Name | Value | Remarks |
ACC_PUBLIC | 0x0001 | pubilc,类外可访问。 |
ACC_PRIVATE | 0x0002 | private,类内才可访问。 |
ACC_PROTECTED | 0x0004 | protected,类和其子类可访问。 |
ACC_STATIC | 0x0008 | static,静态内部类。 |
ACC_FINAL | 0x0010 | final,不能有子类。 |
ACC_INTERFACE | 0x0200 | 接口,同时需要设置:ACC_ABSTRACT。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_Enum |
ACC_ABSTRACT | 0x0400 | 抽象类,无法实例化。不可和ACC_FINAL同时设置。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
ACC_ANNOTATION | 0x2000 | 注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT |
ACC_ENUM | 0x4000 | 枚举类型 |
2.10.2 EnclosingMethod Attribute
当且仅当一个类是匿名类或者本地类(local class),该类才会包含一项且仅有一项EnclosingMethod Attribute。
然而什么是本地类(local class)呢?我的理解,所谓本地类就是在方法内部定义的类,如以下类的定义:
class A {
public Iterator getIterator() {
class LocalClass {
}
return new Iterator() {
public boolean hasNext() { return false; }
public Object next() { return null; }
public void remove() { }
};
}
}
匿名类A$1.class中的EnclosingMethod Attribute:
Enclosing Method: #29 #31 org/levin/insidejvm/miscs/instructions/A.getIterator()Ljava/util/Iterator;
本地类A$1LocalClass.class中的EnclosingMethod Attribute:
Enclosing Method: #22 #24 org/levin/insidejvm/miscs/instructions/A.getIterator()Ljava/util/Iterator;
EnclosingMethod Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“EnclosingMethod”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(4)。 |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义当前类所在方法的宿主类。如上例中,A$1.class和A$1LocalClass.class中的class_index都指向类A。 |
u2 | method_index | 若当前类没有被方法方法包含,如当前类是赋值给类成员的匿名类,则method_index值为0,否则该method_index的值为constant_pool中的索引,CONSTANT_NameAndType_info类型。记录了class_index指定的类中定义的包含当前类的方法名和类型信息。 |
注:这里同样也有一个问题,就是EnclosingMethod Attribute存在的目的问题。我现在的理解,该Attribute的存在也应该只是为了用于反射信息和反编译时可以更好的还原原来代码的结构,在虚拟机运行该程序的时候,由于所有的指令已经编译好了,虚拟机应该不需要这些信息。但是事实是这样的吗?有待考证。
2.10.3 SourceFile Attribute
SourceFile Attribute用于记录和当前字节码对应的源代码的文件(由编译器产生,该Attribute只是记录相应源代码的文件名,而不记录和路径相关的信息)。一个ClassFile中只能包含一项SourceFile Attribute。
SourceFile Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“SourceFile”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(2)。 |
u2 | sourcefile_index | constant_pool中的索引,CONSTANT_Utf8_info类型。记录相应的源代码文件名。 |
2.10.4 SourceDebugExtension Attribute
SourceDebugExctension Attribute是Java为调试时提供的扩展信息,主要用于自定义(扩展)的编译器和调试器。一个ClassFile中只能包含一项SourceFile Attribute。
SourceDebugExtension Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“SourceDebugExtension”)。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
u1 | debug_extension[attribute_length] | Utf-8格式的字符串记录扩展调试信息,不以0结尾。 |
2.10.5 Synthetic Attribute
参见2.11.1
2.10.6 Signature Attribute
参见2.11.2
2.10.7 Deprecated Attribute
参见2.11.3
2.10.8 RuntimeVisibleAnnotations Attribute
参见2.11.4
2.10.9 RuntimeInvisibleAnnotations Attribute
参见2.11.5
2.11 在ClassFile、method_info、field_info中同时存在的Attribute
2.11.1 Synthetic Attribute
Synthetic Attribute用于指示当前类、接口、方法或字段由编译器生成,而不在源代码中存在(不包含类初始函数和实例初始函数)。相同的功能还有一种方式就是在类、接口、方法或字段的访问权限中设置ACC_SYNTHETIC标记。
Synthetic Attribute由JDK1.1中引入,以支持内嵌类和接口(nested classes and interfaces)。但是以我现在所知,这些功能都是可以通过ACC_SYNTHETIC标记来表达的,为什么还需要存在Synthetic Attribute呢?在什么样的情况下会生成Synthetic Attribute项呢?我还没有找到,需要继续研究。
Synthetic Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Synthetic”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(0)。 |
2.11.2 Signature Attribute
Signature Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Signature”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(2)。 |
u2 | signature_index | constant_pool中的索引,CONSTANT_Utf8_info类型。记录当前类型的签名(类签名、字段签名、方法签名)。 |
JVM规范中没有指定什么情况下需要生成Signature Attribute。但是从Signature的目的是用于泛型类型,可以推测Signature Attribute存在于当前Signature Attribute所在类型是泛型(泛型类、泛型方法、泛型字段)的时候。它和field_info、method_info、this_class一起对应于局部变量中的LocalVariableTable Attribute和LocalVariableTypeTable Attribute,他们同时都有descriptor版本和signature版本。
2.11.3 Deprecated Attribute
Deprecated Attribute指示当前类、方法、字段已经过时了,一些工具,如编译器可以根据该Attribute提示用户他们使用的类、方法、字段已经过时了,最好使用最新版本的类、方法、字段。
Deprecated Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Deprecated”)。 |
u4 | attribute_length | 该Attribute内容的字节长度(0)。 |
2.11.4 RuntimeVisibleAnnotations Attribute
RuntimeVisibleAnnotations Attribute记录了当前类、方法、字段在源代码中定义的、在运行时可见的Annotation。Java程序可以通过反射函数获取这些Annotation。一个attributes集合中只能包含一项RuntimeVisibleAnnotations Attribute,记录所有运行时可见的Annotation。
RuntimeVisibleAnnotations Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“RuntimeVisibleAnnotations”)。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
u2 | num_annotations | annotations集合长度。 |
annotation | annotations[num_annotations] | 记录所有运行时可见的annotation的集合。annotation类型详见附录E。 |
2.11.5 RuntimeInvisibleParameterAnotations Attribute
RuntimeInvisibleAnnotations Attribute记录了当前类、方法、字段在源代码中定义的、在运行时不可见的Annotation。默认情况下,这些Annotation是不可被Java提供的反射函数获取的,需要通过和实现相关的机制来获取这些Annotation。一个attributes集合中只能包含一项RuntimeInvisibleAnnotations Attribute,记录所有运行时不可见的Annotation。
RuntimeInvisibleAnnotations Attribute | ||
type | descriptor | remark |
u2 | attribute_name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“RuntimeInvisibleAnnotations”)。 |
u4 | attribute_length | 该Attribute内容的字节长度。 |
u2 | num_annotations | annotations集合长度。 |
annotation | annotations[num_annotations] | 记录所有运行时不可见的annotation的集合。annotation类型详见附录E。 |
总体格式
magic(0xCAFEBABE) | |||||||||||||||||||||
version(major.minor) | |||||||||||||||||||||
constant pool
|
|||||||||||||||||||||
access_flags | this_class | super_class | interfaces | ||||||||||||||||||
fields
|
|||||||||||||||||||||
methods
|
|||||||||||||||||||||
attributes
|
附件A :Java字节码中的类和接口名
在Java字节码中类和接口名主要表现以下几点:
1. 类和接口名都是以全限定名的方式存放(包名加类或接口名)。
2. 在源代码中的点分隔符(”.”)在字节码中以斜杠(”/”)代替。如:“java.lang.Object”-> “java/lang/Object”
3. 数组类型名做了特殊处理。如:“int[][]”-> “[[I”、“Thread[]”->“[Ljava/lang/Thread”。详见附录B:Java字节码中的数组类型名
附件B : Java字节码中的数组类型名
在Java中,数组被认为是类,因而它也有对应的类名表示,而Java字节码为数组名指定了特定的格式:
1. 所有数组名都以“[”开头,n维数组有n个“[”。
2. 对引用类型的数组,在“[”后加“L”后加引用类型的全限定名。
3. 对基本类型,在“[”后加基本类型的对应字符。
基本类型对应字符表 | |
基本类型 | 对应字符 |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
附件C : 描述符(Descriptor)
描述符(Descriptor)定义了字段或方法的类型(A descriptor is a string representing the type of a field or method.这段描述感觉不怎么精确)。它存放在constant pool中的CONSTANT_Utf8_info类型项中。
1. 字段描述符(Field Descriptor)
字段描述符是定义了字段、局部变量、参数等类型的字符串。即附录A中的类或接口名。
语法定义:
FieldDescrptor :
FieldType
BaseType : B、C、D、F、I、J、S、Z(参考附录B中的基本类型对应字符表)
ObjectType : LfullClassName;
ArrayType : [+BaseType | [+ObjectType
FieldType : BaseType | ObjectType | ArrayType
如:[[D -> double[][]、[Ljava/lang/Thread; -> Thread[]、I->int、Ljava/lang/Object; -> Object
2. 方法描述符(Method Descriptor)
方法描述符是定义了方法参数、方法返回等信息的字符串。
语法定义:
MethodDescriptor:
(ParameterDescriptor*)ReturnDescriptor
ParameterDescriptor : FieldType
ReturnDescriptor : FieldType | VoidDescriptor
VoidDescriptor : V
如:void method(int i, Object obj)-> (ILjava/lang/Object;)V
Object getValue()-> ( )Ljava/lang/Object;
Object mymethod(int i, double d, Object o) -> (IDLjava/lang/Object;)Ljava/lang/Object;
附件D : 签名(Signature)
签名(Signature)定义了类、字段或方法的泛型类型信息(A signature is a string representing the generic type of a field or method, or generic type information for a class declaration. 这段描述感觉不怎么精确)。它也存放在constant pool中的CONSTANT_Utf8_info类型项中。
它存在于Signature Attribute中,只有包含泛型的类、字段、方法才会产生Signature Attribute。
签名信息并不是给JVM用的,而是用于编译、调试、反射。
1. 类签名
语法定义:
ClassSignature:
FormalTypeParametersopt SuperclassSignature SuperinterfaceSignature*
FormalTypeParameters:
<FormalTypeParameter+>
FormalTypeParameter:
Identifier ClassBound InterfaceBound*
ClassBound:
: FieldTypeSignatureopt
InterfaceBound:
: FieldTypeSignature
SuperclassSignature:
ClassTypeSignature
SuperinterfaceSignature:
ClassTypeSignature
FieldTypeSignature:
ClassTypeSignature
ArrayTypeSignature
TypeVariableSignature
ClassTypeSignature:
L PackageSpecifier* SimpleClassTypeSignature
ClassTypeSignatureSuffix* ;
PackageSpecifier:
Identifier / PackageSpecifier*
SimpleClassTypeSignature:
Identifier TypeArgumentsopt
ClassTypeSignatureSuffix:
. SimpleClassTypeSignature
TypeVariableSignature:
T Identifier ;
TypeArguments:
<TypeArgument+>
TypeArgument:
WildcardIndicatoropt FieldTypeSignature
*
WildcardIndicator:
+
–
ArrayTypeSignature:
[TypeSignature
TypeSignature:
FieldTypeSignature
BaseType
以上定义没有看懂??例子如:
对class MyClass<T> { } 定义的类,产生如下的签名:
<T:Ljava/lang/Object;>Ljava/lang/Object;
而对以下类定义:
class MyClass<T1, T2> extends ClassFileParser implements IndexParser {
}
则产生如下签名:
<T1:Ljava/lang/Object;T2:Ljava/lang/Object;>Lorg/levin/classfilereader/ClassFileParser;Lorg/levin/classfilereader/IndexParser;
2. 字段签名
语法定义如上,没能看懂。从Tomcat代码中的Digester.class文件中可以解析得到如下的例子:
Ljava/util/HashMap<Ljava/lang/String;Ljava/util/Stack<Ljava/lang/String;>;>;(对应的descriptor:“Ljava/util/HashMap;”)
Ljava/util/Stack<Ljava/lang/Object;>;(对应的descriptor:“Ljava/util/Stack;”)
3. 方法签名
语法定义:
MethodTypeSignature:
FormalTypeParametersopt (TypeSignature*) ReturnType
ThrowsSignature*
ReturnType:
TypeSignature
VoidDescriptor
ThrowsSignature:
^ClassTypeSignature
^TypeVariableSignature
也没能看懂。同样从Tomcat代码中的Digester.class文件中可以解析得到如下例子:
(Ljava/lang/String;Ljava/lang/Class<*>;Ljava/lang/String;)V(对应descriptor:“(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)V”)
(Ljava/lang/String;Ljava/lang/Class<*>;Ljava/lang/String;Z)V(对应descriptor:“(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Z)V”)
()Ljava/util/Map<Ljava/lang/String;Ljava/net/URL;>;(对应descriptor:“()Ljava/util/Map;”)
附录E:annotation结构和element_value结构
1. annotation结构
每一项annotation结构记录一项用户定义的annotation的值。如:
@Test(id = 4, description = “description”, useCase = @UseCase())
@UseCase()
void testExecute(int a) {
}
编译器会为该方法生成两项annotation。每项annotation指定了annotation的类型和键值对。
annotation结构 | |||||||||||||||||
type | descriptor | remark | |||||||||||||||
u2 | type_index | constant_pool中的索引。CONSTANT_Utf8_info类型。以字段描述符(field descriptor)方式记录当前结构表示的annotation类型。 | |||||||||||||||
u2 | num_element_value_pairs | 记录当前annotation中的键值对数。 | |||||||||||||||
|
2. element_value结构
element_value结构记录了所有annotation类型的键值对中的值。它是一个联合类型,可以表示多种类型的值。
element_value结构 | ||||||||||||||||||||||||||||||||||||||||||||||||||
type | descriptor | remark | ||||||||||||||||||||||||||||||||||||||||||||||||
u1 | tag | tag记录了当前annotation键值对中值的类型,’B’、’C’、’D’、’F’、’I’、’J’、’S’、’Z’表示基本类型(见附录B中的基本类型对应表);其他的合法值有:
’s’ -> String ‘e’ -> enum constant ‘c’ -> class ‘@’ -> annotation type ‘[‘ -> array |
||||||||||||||||||||||||||||||||||||||||||||||||
|
注:从这个结构中,我们也可以得出annotation中可以设置的值类型:
1. 基本类型值(byte、char、double、float、int、long、short、boolean)
2. 字符串(String)
3. 枚举(enum)
4. 类实例(Class)
5. 嵌套注解类型(annotation)
6. 以上所有以上类型的一维数组。
本文出自 传播、沟通、分享,转载时请注明出处及相应链接。
本文永久链接: https://www.nickdd.cn/?p=1726