ASM是一款基于JAVA字节码层面的代码分析和修改工具;无需提供源代码即可对应用增添所需的Debug代码。用于应用API性能分析,代码优化和代码混淆等工作。ASM的目标是生成,转换和分析已编译的JAVA class文件,可使用ASM工具读、写、转换JVM指令集。
ASM工具提供两种方式来产生和转换已编译的class文件:
基于事件的表示模型
使用一个有序的事件序列表示一个class文件,class文件中的每一个元素使用一个事件来表示,比如class的头部,变量,方法声明,JVM指令都有相对应的事件表示,ASM使用自带的事件解析器能将每一个class文件解析成一个事件序列。基于对象的表示模型
则使用对象树结构来解析每一个class文件。
基于事件模型的ASM工具使用生产者-消费者
模型转换/产生一个class文件。其转换过程中涉及到自定义的事件生产者
,自定义的事件过滤器
和自定义的事件消费者
这三种组件。其中使用ClassReader
来解析每一个class文件中的事件元素;使用自定义的各种基于方法/变量/声明/类注释的元素适配器
来过滤和修改class事件元序列中的相应事件对象;最后使用ClassWriter
来重新将更新后的class事件序列转换成class字节码供JVM加载执行。整个生产/转换class文件的过程如下图所示,起点和终点分别是ClassReader(class文件解析器)和ClassWriter(class事件序列转换到class字节码),中间的过程由若干个自定义的事件过滤器组成。
class文件的结构
class文件保持固定的结构信息,而且保留了几乎所有的源代码文件中的符号。一个class文件整体结构由几个区域组成,一个区域用来描述类的modifier,name,父类,接口和注释。一个区域用来描述类中变量的modfier,名字,类型和注释。一个区域用来描述类中方法和构造函数的modifier,名字参数类型,返回类型,注释等信息,当然也包含已编译成java字节码指令序列的方法具体内容。还有一个作为class文件的静态池区域,用来保存所有的数字,字符串,类型的常量,这些常量只被定义过一次且被其他class中区域所引用。class文件与源代码文件的关系:一个java文件最后会被编译成N(1 <= N)个class文件。
下表展示了一个class文件的总体概貌:
Modifiers, name, super class, interfaces | |
Constant pool: numeric, string and type constants | |
Source file name(optional) | |
Enclosing class reference | |
Annotation* | |
Attribute* | |
Inner class* | Name |
Field* | Modifiers, name, type |
Annotation* | |
Attribute* | |
Method* | Modifiers, name, return and parameter types |
Annotation* | |
Attribute* | |
Compiled code |
class文件的内部命名
原Java类型与class文件内部类型对应关系:
Java type | Type descriptor |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
long | J |
double | D |
Object | Ljava/lang/Object; |
int[] | [I |
Object[][] | [[Ljava/lang/Object; |
原Java方法声明与class文件内部方法声明的对应关系:
Method declaration in source file | Method descriptor |
---|---|
void m(int i, float f) | (IF)V |
int m(Object o) | (Ljava/langObject;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int [] i) | ([I)Ljava/lang/Object; |
ASM工具的接口和组件
ASM工具生产和转换class文件内容的所有工作都是基于ClassVisitor
这个抽象类进行的。ClassVisitor抽象类中的每一个方法会对应到class文件的相应区域,每个方法负责处理class文件相应区域的字节码内容。
1 | public abstract class ClassVisitor { |
某些区域方法直接完成该区域字节码内容的处理并返回空,某些复杂class区域的方法需返回更加细节的XXXVisitor对象,此XXXVisitor对象将负责处理此class区域的更加细节的字节码内容,开发者可以编写继承自XXXVisitor抽象类的自定义类,在成员函数中实现对细节字节码操作的逻辑代码。比如,visitField方法用来负责class文件中变量区域的字节码内容修改,该区域又可细分出多种属性数据对象(注释,参数值),这里需要编写继承自FieldVisitor抽象类的自定义类完成这些细分数据对象的字节码内容操作。
1 | public abstract class FieldVisitor { |
基于ClassVistor API的访问方式,ASM工具提供了三种核心组件用来实现class的产生和转换工作。ClassReader
负责解析class文件字节码数组,然后将相应区域的内容对象传递给ClassVisitor
实例中相应的visitXXX方法,ClassReader可以看作是一个事件生产者。ClassWriter
继承自ClassVistor抽象类,负责将对象化的class文件内容重构成一个二进制格式的class字节码文件,ClassWriter可以看作是一个事件消费者。继承自ClassVistor抽象类的自定义类负责class文件各个区域内容的修改和生成,它可以看作是一个事件过滤器,一次生产消费过程中这样的事件过滤器可以有N个(0<=N)。
遍历class字节码类信息
下面的例子用来打印class字节码内容,这里以java.lang.runnable为例。
ClassPrinter.java
1 | import org.objectweb.asm.FieldVisitor; |
输出:
1 | java/lang/Runnable extends java/lang/Object { |
产生自定义类对应的class字节码
需要产生如下接口的字节码:
1 | package pkg; |
所需代码如下:
ClassGenerator.java
1 | import java.io.File; |
增加class中的方法或者变量
在visitEnd中增加想要增加的方法或者变量,首先,实现如下类:
1 | package com.asm5; |
目标就是为这个类的class文件添加一个计时器,修改成如下效果:
1 | package com.asm5; |
我们需要在m()方法的最前面增加指令,在RETURN指令前增加指令,同时指令必须位于xRETURN和ATHROW之前,因为这些指令都会结束方法的执行。
实现元素适配器:
AddTimeClassAdapter.java
1 | package com.asm5; |
修改class字节码文件:
Generator.java
1 | package com.asm5; |
下面是一个测试类:
1 | package com.asm5; |
输出结果为:100
ASM工具的作用还远不止如此,这里只是为了学习写的一些简单的用法,后面会写具体的应用,用到其他功能时再补充。还有:学习一个新的技术或工具时,有官方文档一定要多看官方文档!
reference
http://www.cnblogs.com/liuling/archive/2013/05/25/asm.html
http://blog.csdn.net/xysmiracle/article/details/38293795
http://asm.ow2.org/asm50/javadoc/user/index.html