ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为,ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。本文我们使用asm框架生成一个字节码并调用生成类中的方法。关于更详细的指令可以参考java虚拟机规范
使用ASM框架需要导入asm的jar包,下载链接:asm-3.2.jar。
public class Shareniu { public static void main(String[] args) { System.out.println("hello shareniu"); } }
下面我们就开始考虑如何生成上述类的字节码,具体步骤如下:
2.自定义一个类继承ClassLoader并实现Opcodes接口(asm包中的),实例代码如下所示:
public class Helloworld extends ClassLoader implements Opcodes {
3.首先,我们需要生成Shareniu类,该类访问级别是public,所有的类如果没有显式的指定父类,则默认都是Object类,也就是java/lang/Object。该字节码可以使用jdk8版本进行编译。那好我们来生成以下,实例代码如下:
ClassWriter cw = new ClassWriter(0); //生成类 指定当前类的访问级别,jdk版本 类名、父类。 cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Shareniu", null, "java/lang/Object", null);
4.为Shareniu类生成一个无参的构造函数,因为我们并没有显式的制定无参构造函数,所以我们需要自己搞一个无参构造函数,这也是jvm内部的实现机制,实例代码如下:
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); //因为是实例方法,所以卡槽中的第一个位置是this //指令 0: aload_0 mw.visitIntInsn(ALOAD, 0); //调用父类的构造函数 类似 1: invokespecial #8 // Method java/lang/Object."<init>":()V mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); //返回 4: return mw.visitInsn(RETURN);
//下面代码试用了最多一个堆栈元素以及局部变量 : Code: stack=1, locals=1, args_size=1
mw.visitMaxs(1, 1); mw.visitEnd();
2.将this加载到卡槽中,并且是第一个卡槽,注意:实例方法中第一个卡槽位置肯定是this,静态方法则不需要。
5.生成public static void main(String[] args) {}方法,实例代码如下:
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); //获取PrintStream 用来打印流 // 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); //从常量池获取 hello shareniu 并将其推送到栈顶 3: ldc #22 mw.visitLdcInsn("hello shareniu "); //调用println方法 5: invokevirtual #24 mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // 8: return mw.visitInsn(RETURN); //Code: // stack=2, locals=1, args_size=1 mw.visitMaxs(2, 2); mw.visitEnd();
上述代码逻辑如下:
1.生成 public static void main方法,参数是数组,这个注意一下。
3.从常量池获取 hello shareniu 并将其推送到栈顶。
4.通过INVOKEVIRTUAL调用PrintStream类中的println方法。
byte[] code = cw.toByteArray();
Helloworld loader = new Helloworld(); Class<?> exampleClass = loader.defineClass("Shareniu", code, 0, code.length); exampleClass.getMethods()[0].invoke(null, new Object[] { null });
byte[] code = cw.toByteArray(); FileOutputStream fos = new FileOutputStream("Shareniu.class"); fos.write(code); fos.close();
转载请注明:分享牛 » asm生成类的字节码1