2.1 ASM-类-结构
本文可转载演绎,但需要注明原作者和本文链接。
本章介绍了使用ASM core的API,生成编译后的class和转换编译后的class。
首先展示了编译后的class文件,然后介绍相应的ASM接口、组件和工具类来生成和转换这些class,并且附带了很多说明性的事例。
方法、注释、和Generics(TODO 需要找一个合理的翻译)会在接下来的章节介绍。
2.1 结构
2.1.1 概览
编译类的整体结构是十分简单的。事实上,不同于本地编译的应用程序,一个编译后的class保留了结构化的信息和源码中几乎所有的符号(symbol)引用。
事实上,一个编译的class包含以下部分:
- 描述修饰符的部分(例如public、private),类名、父类、实现的接口集合和类的注解(annotation)。
- class声明的属性部分。每一个部分都包括修饰符、属性名、属性类型和属性的注解。
- class声明的构造函数和方法部分。每一个部分都包括修饰符、方法名、返回类型、参数类型和方法的注解。它还包含了方法编译后的字节码信息,由一系列的字节码指令集组成。
源码和编译后的class之间有一些不同之处:
- 一个编译后的class仅仅描述一个类,然后一个源码文件可以包含几个类的声明。例如一个源码文件可以声明一个主类和一个该类的内部类,在编译后会成为两个class文件:一个表示该主类,另一个表示内部类。
然而该主类文件中包含了对内部类的引用,并且内部类定义的内部方法包含了对他们封闭方法的引用。(TODO inner classes defined inside methods contain a reference to their enclosing method.) - 一个编译后的class不包含注释,但是包含了类、属性、方法和代码这些元素所关联的附加属性。自从Java 5中引入了注解,起到了相同的作用后,附加属性就几乎不再被使用了。
- 一个编译后的class不包含package声明和import声明部分,因此所有的类型名称必须是全路径的。
另一个非常重要的结构不同是一个编译后的class包含了常量池部分。常量池是一个数组,包括该类中所有出现的数字型、字符串和类型的常量。
这些常量在常量池中只会被定义一次,在class文件的其他部分使用数组的索引来关联该常量值。
幸运的是,ASM隐藏了所有常量池相关的细节,因此你就不需要关心常量池了。表格2.1总结了编译后class的整体结构,确切的结构可以在Java虚拟机规范第四章中找到。
表格2.1 :编译后的class结构(*表示0个或者多个)
类结构 | |
---|---|
修饰符,类名,父类,接口 | |
常量池:数值、字符串、类型常量 | |
源文件名称(可选) | |
封闭的方法引用 | |
注解* | |
Attribute* | |
内部类* | 名称 |
类属性* | 修饰符、名称、类型 |
注解 | |
Attribute | |
方法* | 修饰符、名称、返回值类型和参数类型 |
注解 | |
Attribute | |
编译后的code |
另一个重要的区别就是Java类型的表示方式在源码和编译后的class中不同。下一个部分将介绍它们如何在编译后的class中表示。
2.1.2 内部名
在许多情况下,类型被约束成一个类或者接口。
例如一个类的父类、一个类所实现的接口、被方法抛出的异常都不能是基础类型或者数组,一定是类或者接口。
这些类型在编译后class中使用内部名(internal name)表示。
这些内部名称是一个全路径类型,只不过分隔符由英文逗点换成了反斜线。
例如String类型的内部名是java/lang/String
2.1.3 类型描述符
内部名仅仅用于被约束成类或者接口的类型。在所有其他情况下,比如字段类型,Java中的类型在编译后的class中使用类型描述符表示。
类型描述符表格(表格 2.2)如下.
表格2.2 :Java类型描述符
Java类型 | 类型描述符 |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
long | J |
double | D |
void | V |
Object | Ljava/lang/Object; |
int[] | [I |
Object[][] | [[Ljava/lang/Object; |
基础类型的描述符使用单个字符:Z表示boolean,C表示char,B表示byte,S表示short,I表示int,F表示float,J表示long,D表示double。
类的类型描述符就是内部名加上前缀L和后缀分号(;)。例如String的类型描述符是Ljava/lang/String;。
数组类型的描述符就是前缀[加上数组元素的类型描述符。
2.1.4 方法描述符
一个方法描述符是由一系列类型描述符组成的一个字符串,包括方法的参数类型和返回值类型。
方法描述符使用小括号开始,小括号内部是方法入参的类型描述符按照顺序拼接的字符串,在加上返回值的类型描述符组成,返回值是void的时候,使用V。
方法的描述符不包含方法名和参数名。
表格2.3 :一些方法描述符示例
源码中的方法 | 方法描述符 |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I)Ljava/lang/Object; |
只要你理解了类型描述符的转换方法,也很容易理解方法描述符。例如(I)I描述的是一个入参是int类型,返回值是int的方法。