盘古BPM体验地址    盘古BPM交流群盘古BPM交流群号:963222735

asm生成类的字节码1

分享牛 1887℃

ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为,ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。本文我们使用asm框架生成一个字节码并调用生成类中的方法。关于更详细的指令可以参考java虚拟机规范

使用ASM框架需要导入asm的jar包,下载链接:asm-3.2.jar


1.首先看一下,我们需要生成的类字节码源文件,如下所示:

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();

上述代码逻辑如下:

1.自己生成无参构造函数 <init>,返回值是Void

2.将this加载到卡槽中,并且是第一个卡槽,注意:实例方法中第一个卡槽位置肯定是this,静态方法则不需要。

3.访问父类的无参构造函数 <init>。

4.直接返回。

5.堆栈元素以及局部变量,这两个后续详细说明。

 

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方法,参数是数组,这个注意一下。

2.通过GETSTATIC获取到PrintStream类。

3.从常量池获取 hello shareniu 并将其推送到栈顶。

4.通过INVOKEVIRTUAL调用PrintStream类中的println方法。

5.堆栈元素以及局部变量,这两个后续详细说明。

6.获取字节码的byte数组,实例代码如下:

byte[] code = cw.toByteArray();

7.运行生成的字节码,示例代码如下:

        Helloworld loader = new Helloworld();
        Class<?> exampleClass = loader.defineClass("Shareniu", code, 0, code.length);
        exampleClass.getMethods()[0].invoke(null, new Object[] { null });


不出意外的话,控制台的输出如下:

hello shareniu

8.将byte数组写入到指定文件,示例代码如下:

byte[] code = cw.toByteArray();
FileOutputStream fos = new FileOutputStream("Shareniu.class");
fos.write(code);
fos.close();

 

转载请注明:分享牛 » asm生成类的字节码1