创建类并实现接口
创建类并实现接口
前面我们已经学习了如何使用ASM创建一个类并为类添加方法。实现接口也非常简单,只需要在调用类访问器的visit方法时,指定该类需要实现的接口,而实现接口定义的方法就是使用visitMethod方法为该类添加一个访问标志与接口中定义的方法相同、方法名称与方法描述符都相同的方法。
以实现SayHelloInterface接口为例,SayHelloInterface接口的定义如下。
public interface SayHelloInterface { void sayHello(); }
现在我们使用ClassWriter创建一个新的类,类名为SayHelloInterfaceImpl,并为该类实现SayHelloInterface接口。实现SayHelloInterface接口的sayHello方法,为sayHello插入输出“hello word”的字节码指令。如代码清单5-16所示。
代码清单5-16 使用ClassWriter创建SayHelloInterfaceImpl类
public class UseAsmImpInterface { public static void main(String[] args) throws IOException { // 创建的类的类名 String implClassName = SayHelloInterface.class.getName() + "Impl"; ClassWriter cw = new ClassWriter(0); // 设置class文件结构的版本号、类名、类签名、父类、实现的接口 cw.visit(Opcodes.V1_8, ACC_PUBLIC, implClassName.replace(".", "/"), null, Type.getInternalName(Object.class), new String[]{Type.getInternalName(SayHelloInterface.class)}); // 创建asyHello方法 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()V", null, null); // 插入输出"hello word!"的字节码指令 mv.visitFieldInsn(GETSTATIC, Type.getInternalName(System.class), "out", Type.getDescriptor(System.out.getClass())); mv.visitLdcInsn("hello word!"); mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(System.out.getClass()), "println", "(Ljava/lang/String;)V", false); // void方法也需要有return指令 mv.visitInsn(RETURN); // 设置局部变量表和操作数栈的大小 mv.visitMaxs(2,1); // 获取生成的类的字节数组 byte[] byteCode = cw.toByteArray(); // 保存到文件 ByteCodeUtils.savaToFile(implClassName, byteCode); } }
在创建ClassWriter时,我们给构造方法传递的参数值为0,意味着我们需要自己计算方法的局部变量表和操作数栈的大小,所以在创建sayHello方法时,需要调用方法访问者的visitMaxs方法设置局部变量表和操作数栈的大小。
那为什么操作数栈的大小是2,局部变量表的大小是1呢?我们一共为sayHello方法添加了三条指令,第一条指令是获取一个静态字段,指令返回字段的引用存储在操作数栈顶,接着将字符串“hello word!”的引用放入栈顶,最后才调用PrintStream的println方法,因此操作数栈的深度至少为2才能完成println方法的调用。而局部变量表只存储this引用,所以设置为1。
代码清单5-16中还用到了ASM提供的工具类Type的一些方法:
◆getInternalName:获取一个类的内部类型名称,比如我们调用String.class.getName方法获取到的名称是“java.lang.String”,调用Type的getInternalName方法获取到的就是“java/lang/String”;
◆getDescriptor:获取类的描述符,如String类的描述符为“Ljava/lang/String;”。
运行代码清单5-16中的main方法,我们能够得到一个SayHelloInterfaceImpl类,但是这个类并不能加载使用,因为没有生成方法。SayHelloInterfaceImpl类如图5.7所示。

图5.7 生成的SayHelloInterfaceImpl类