?
2.2.3生成类
生成一个类只需要ClassWriter组件即可。下面将使用一个例子来展示。考虑下面的接口:
package pkg;
public interface Comparable extends Mesurable {
???????? int LESS = -1;
???????? int EQUAL = 0;
???????? int GREATER = 1;
???????? int compareTo(Object o);
}
?
上面的类可以通过调用ClassVisitor的6个方法来生成:
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
???????? "pkg/Comparable", null, "java/lang/Object",
???????? new String[] { "pkg/Mesurable" });
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
???????? null, new Integer(-1)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
???????? null, new Integer(0)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
???????? null, new Integer(1)).visitEnd();
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
???????? "(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
byte[] b = cw.toByteArray();
?
第一行代码用于创建一个ClassWriter实例,由它来构建类的字节数组(构造方法中的参数将在后面章节介绍)。
?
首先,通过调用visit方法来定义类的头部。其中,V1_5是一个预先定义的常量(与定义在ASM Opcodes接口中的其它常量一样),它定义了类的版本,Java 1.5.ACC_XX常量与java中的修饰符对应。在上面的代码中,我们指定了类是一个接口,因此它的修饰符是public和abstract(因为它不能实例化)。接下来的参数以内部名称形式定义了类的名称,(见2.1.2章节)。因为编译过的类中不包含package和import段,因此,类名必须使用全路径。接下来的参数与泛型对应(见4.1章节)。在上面的例子中,它的值为null,因为这个接口没有使用泛型。第五个参数指定了父类,也是以内部形式(接口隐式地继承自Object)。最后一个参数指定了该接口所继承的所有接口,该参数是一个数组。
?
接下来三次调用visitField方法,都是用来定义接口中三个字段的。visitField方法的第一个参数是描述字段的访问修饰符。在这里,我们指定这些字段为public,final和static。第二个参数是字段的名称,与在源代码中的名称一样。第三个参数,以类型描述符的形式指定了字段的类型。上面的字段是int类型,因此它的类型描述符是I。第四个参数与该字段的泛型对应,在这里为空,因为这个字段没有使用泛型。最后一个参数是这些字段的常量值,这个参数只能针对常量字段使用,如final static类型的字段。对于其他字段,它必须为空。因为这里没有使用注解,所以没有调用任何visitAnnotation和visitAttribute方法,而是直接调用返回的FieldVisitor的visitEnd方法。
?
visitMethod方法是用来定义compareTo方法的。该方法的第一个参数也是定义访问修饰符的,第二个参数是方法的名称,在源代码中指定的。第三个参数是该方法的描述符,第三个参数对应泛型,这里仍然为空,因为没有使用泛型。最后一个参数是指定该方法所声明的异常类型数组,在这个方法中为null,因为compareTo方法没有声明任何异常。visitMethod方法返回一个MethodVisitor(参见图3.4),它可以用来定义方法的注解和属性,以及方法的代码。在这里没有注解,因为这个方法是抽象的,因此我们直接调用了MethodVisitor的visitEnd方法。
?
最后,调用ClassWriter的visitEnd方法来通知cw类已经完成,然后调用toByteArray方法,返回该类的字节数组形式。
?
使用生成的类
前面获取的字节数组可以保存到Comparable.class文件中,以便在以后使用。此外它也可以被ClassLoader动态加载。可以通过继承ClassLoader,并重写该类的defineClass方法来实现自己的ClassLoader:
class MyClassLoader extends ClassLoader {
???????? public Class defineClass(String name, byte[] b) {
?????????????????? return defineClass(name, b, 0, b.length);
???????? }
}
然后可以通过下面的代码来加载:
Class c = myClassLoader.defineClass("pkg.Comparable", b);
?
另一种加载生成的类的方法是通过定义一个ClassLoader的子类,并重写其中的findClass方法来生成需要的类:
class StubClassLoader extends ClassLoader {
???????? @Override
???????? protected Class findClass(String name)
?????????????????? throws ClassNotFoundException {
?????????????????? if (name.endsWith("_Stub")) {
??????????????????????????? ClassWriter cw = new ClassWriter(0);
??????????????????????????? ...
??????????????????????????? byte[] b = cw.toByteArray();
??????????????????????????? return defineClass(name, b, 0, b.length);
?????????????????? }
?????????????????? return super.findClass(name);
???????? }
}
实际上使用生成类的方式取决于使用的上下文,它超出了ASM API的范围。如果你打算写一个编译器,那么类的生成过程将被一个即将被编译的类的抽象的语法树驱动,并且生成的类将被保存到磁盘上。如果你打算编写一个动态的类代理生成工具或者在面向切面编程中使用,那么选择ClassLoader比较合适。