基本概念
AMS库的目标是生成、转换和解析已经编译好的Java类,表现为byte数组(存储在磁盘上,被JVM加载)。为此,ASM提供了比字节更高级的概念来读取、写入和转换此类字节数组的工具,例如:数字常亮,字符串,Java标识符,Java类型,Java类结构元素。特别需要注意的是,ASM库的作用范围仅限于读、写、转换、分析类,类加载过程是超出了这个范围的。
ASM库提供了两种API用来生成和转换编译的class:core API提供了基于事件的类表示,tree API提供了基于对象的类表示。
在基于事件的模型中,一个类由一系列的事件表示,每一个事件代表了类的元素,例如:类的头,字段,方法声明,指令等等。基于事件的API定义了一组可能的事件以及事件的发生顺序,这种解析器为每一个被解析的元素生成了一个事件,以及一个类编写器用来生成编译好的class。
在基于对象的模型中,用对象树表示一个类,每个对象表示该类的一部分,例如:类本身,字段,方法,指令等。每个对象都会有这些属性的引用,用来表示他的组成成分。基于对象的API提供了一种将表示一个类的时间序列转换为对象树的方法,反之亦然。也就是说,基于对象树的API是建立在基于事件的API之上的。
这两个API和XML的SAX api和xml的DOM api的关系类似:基于事件事件的API类似于SAX,基于对象的API类似于DOM。基于对象的API建立在基于事件的API之上,就像在SAX基础上提供DOM一样。
ASM提供的这两种API各有优缺点:
- 与基于对象的API相比,基于事件的API更快,而且需要更小的内存。因为不需要创建并在内存中存储对象树。
- 基于事件的API难以实现类转换,因为在任何给定的事件只有该类的一个元素可以用(因为是基于事件的,所以只会存在该事件对应的元素),基于对象的API在内存中有完整的类信息。
注意,这两个API同一时间只能管理一个类:不维护类的结构信息,对某个类修改如果影响其他类了,其他类也需要用户自己修改。
ASM的库分到了不同的包里:
-
org.objectweb.asm and org.objectweb.asm.signature包定义了基于事件的API,提供了类解析、类写入组件。
-
org.objectweb.asm.util包提供了基于core api的一些工具,用来辅助开发和debug用。
-
org.objectweb.asm.commons提供了一些预定义的类信息,大部分都是基于core api的。
-
org.objectweb.asm.tree定义了基于对象的API,提供了基于对象API和基于事件API之间的转换工具。
-
org.objectweb.asm.tree.analysis包提供了基于树API的类解析框架。
CORE API
Classes
这一章说明如何使用core API生成和转换已编译的Java类。
Structure
编译类的整体结构非常简单。实际上,与本地编译应用不同,编译的类保留了结构信息和源代码中的几乎所有符号。实际上,一个已经编译的类包含:
- 描述修饰符,例如public或者private这种,像名称,父类,接口和注解
- 类中定义的每个字段。每个部分都有修饰符,名称,类型和注解
- 类中的声明的每个方法和构造函数。每个部分都包括了修饰符,名称,返回和参数类型,以及方法的注解。他还以字节码的形式包含了该方法的已编译的代码。
但是,源类和编译后的类还是有一些差异的:
- 编译的类仅能描述一个类,但是源文件可以有多个类。例如一个包含内部类的源文件会被编译为两个类文件:一个主类,一个内部类。但是主类文件会包含对内部类的引用,内部类的闭包方法也会包含主类的引用
- 编译的类不包含注释
- 编译的类不包含package和import内容,因此所有的类型名称都是完全限定的
另一个非常重要要的差异是,已编译的类包含常量池。这个池是一个包含了该类中出现的所有数字、字符串和类型常量的数组。这些常量仅在常量池中定义一次,并且在类文件的所有其他部分由他们的索引引用。幸运的是ASM隐藏了与常量池有关的所有详细信息。下面这个图是已编译类的整体结构:
还有一个非常重要的差异是Java类型在编译类和源类红表示方式有所不同。
Internal names
在许多情况下,类型被约束为类或者接口类型。例如,一个类的父类,类实现的接口,或者是一个类抛出的异常,都不能是原始类型或者数组类型,必须是类或者接口类型。这些类型在已编译的类中用Internal names表示。一个类的Internal names就是该类的完全限定名,其中点用斜杠替换。例如,字符串的Internal names就是 java/lang/String。
Type descriptors
Internal names仅用来约束类和接口类型。其他的情况中,Java类型都是由type descriptions描述。基本类型的描述符是单个字符,如下图。类类型的描述符是类名称,前面跟着“L”,后面跟着“;”。数组类型是前面跟着“[”。
Method descriptors
Method descriptors是用一个字符串描述了方法的参数类型和返回类型。方法描述是一个括号,内部是每个形式的参数的type descriptors,然后是返回类型的type descriptors,如果返回void,则是V。
Interfaces and components
用于生成和转换已编译类的ASM API都是继承于ClassVisitor抽象类。该类的方法都是关联到类上的。
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces);
public void visitSource(String source, String debug);
public void visitOuterClass(String owner, String name, String desc);
AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitInnerClass(String name, String outerName,String innerName, int access);
public FieldVisitor visitField(int access, String name, String desc,String signature, Object value);
public MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions); void visitEnd();
}
用于访问属性和注解的都是继承抽象类FieldVisitor。
public abstract class FieldVisitor {
public FieldVisitor(int api);
public FieldVisitor(int api, FieldVisitor fv);
public AnnotationVisitor visitAnnotation(String desc, boolean visible);
public void visitAttribute(Attribute attr);
public void visitEnd();
}
在ClassVisitor类的文档中描述了,该类的调用顺序必须如下:
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )*
visitEnd
也就是说必须先调用visit方法,接着至多调用一次visitSource方法,接着至多调用一次visitOuterClass方法,然后是任意次、任意顺序的调用visitAnnotation和visitAttribute方法,接着是任意次、任意顺序的调用visitInnerClass,visitField,visitMetgod方法,最后是一次调用visitEnd方法结束。
ASM提供了基于ClassVisitor API的核心组件来生成和转换类:
- ClassReader用来解析编译的类
- ClassWriter用来生成编译的类
- ClassVisitor是一个事件过滤器,把每一调用的方法都委托给另一个ClassVisitor实例。
解析类
解析一个类唯一必需的组件是ClassReader。举个例子:
public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(ASM7);
}
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
public void visitSource(String source, String debug) {
}
public void visitOuterClass(String owner, String name, String desc) {
}
public AnnotationVisitor visitAnnotation(String desc,
boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
System.out.println(" " + desc + " " + name);
return null;
}
public void visitEnd() {
System.out.println("}");
}
}
第一步就是继承ClassVisitor这个抽象类,实现抽象方法。
第二步结合ClassPrinter和ClassReader组件,这样由ClassReader读取类时产生的事件会经过我们的ClassPrinter类
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
如上,就会走到我们的ClassVisitor中。
生成类
这部分有其他工具可以简化,所以不太特别需要研究。只需要继承ClassWriter抽象类就可以。当需要使用我们生成的类时,我们需要自定义一个classLoader。这个和ASM无关了,而且热部署相关的业内实践非常多,不过多介绍。
删除一个类的成员
删除一个成员只需要在我们的类中不对这个成员进行转发即可,例如:
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// do not delegate to next visitor -> this removes the method
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
某些方法我们不再继续转发,而是直接返回null,那这个方法就会被删掉了。
增加一个类的成员
直接上代码,可能更清楚,我们只需要对新增加的成员进行一次visit调用就可以。
public class AddFieldAdapter extends ClassVisitor {
private int fAcc;
private String fName;
private String fDesc;
private boolean isFieldPresent;
public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName,
String fDesc) {
super(ASM7, cv);
this.fAcc = fAcc;
this.fName = fName;
this.fDesc = fDesc;
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (name.equals(fName)) {
isFieldPresent = true;
}
return cv.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
}