java ASM

ASM是操作类字节码的工具包,提供ClassVisitor、FieldVisitor、MethodVisito等访问方法

  <dependency>
      <groupId>org.ow2.asm</groupId>
      <artifactId>asm</artifactId>
      <version>6.2</version>
  </dependency>
public class AsmTest {
    public static void main(String[] args) throws Exception {
        b();
    }

    /**
     * asm创建类,方法,字段
     * @throws Exception
     */
    public static void a() throws Exception{
        String className = "classhandle.AsmClass";
        String signature = "L" + className.replace(".", "/") + ";";
        // 创建类,参数值为0,需要自己计算方法的局部变量表和操作数栈的大小,所以在创建方法时,需要调用方法的visitMaxs方法
        //ClassWriter.COMPUTE_MAXS自动计算
        ClassWriter classWriter = new ClassWriter(0);
        /** version:指定类文件结构的版本号;
         access:指定类的访问标志,如public、final等;
         name:指定类的名称(内部类名),如“java/lang/String”;
         signature:类的类型签名,如“Ljava/lang/String;”;
         superName:继承的父类名称。除Object类外,所有的类都必须有父类;
         interfaces:该类需要实现的接口*/
        classWriter.visit(Opcodes.V1_8, 0x0001,
                className.replace(".", "/"),
                signature,
                Object.class.getName().replace(".", "/"),
                null);

        /**
         *操作方法
         access:方法的访问标志,如public、static等;
         name:方法的名称(内部类名);
         descriptor:方法的描述符,如“()V”;
         signature:方法签名,可以为空;
         exceptions:该方法可能抛出的受检异常的类型内部名称,可以为空。*/
        MethodVisitor methodVisitor = classWriter.visitMethod(0x0001,"<init>","()V",null,null);
        /**
         MethodVisitor接口定义的几个常用API:

         visitCode:访问方法的Code属性,实际上也是一个空方法,什么事情也不做;
         visitMaxs:用于设置方法的局部变量表与操作数栈的大小;
         MethodVisitor接口提供的编写字节码指令相关的API:

         visitInsn:往Code属性的code数组中添加一条无操作数的字节码指令,如dup指令、aload_0指令等;
         visitVarInsn:往Code属性的code数组中添加一条需要一个操作数的字节码指令,如aload指令;
         visitFieldInsn:往Code属性的code数组中添加一条访问字段的字节码指令,用于添加putfield、getfield、putstatic、getstatic指令;
         visitTypeInsn:往Code属性的code数组中添加一条操作数为常量池中某个CONSTANT_Class_info常量的索引的字节码指令,如new指令;
         visitMethodInsn:往Code属性的code数组中添加一条调用方法的字节码指令,如invokevirtual指令。

         */
        methodVisitor.visitCode();
        // 调用父类构造器
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        /* *
        visitMethodInsn方法的各参数解析:
        opcode:字节码指令的操作码,如invokesepcial指令的操作码十进制的值为183;
        owner:类的内部类型名称,如“java/lang/Object”;
        name:方法名称,如“”;
        descriptor:方法描述符,如“()V”;
        isInterface:是否是接口,使用invokeinterface指令才传true,其它都传false。
        */
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/Object","<init>", "()V", false);
        // 添加一条返回指令
        methodVisitor.visitInsn(Opcodes.RETURN);
        // 设置操作数栈和局部变量表大小
        methodVisitor.visitMaxs(1,1);

        classWriter.visitEnd();

        /**字段操作
         visitField方法的各参数说明:

         access:字段的访问标志,如public、final、static;
         name:字段的名称;
         descriptor:字段的类型描述符,如”Ljava/lang/String;”;
         signature:字段的类型签名;
         value:字段的初始值,此参数只用于静态字段,如接口中声明的字段或类中声明的静态常量字段,并且类型必须是基本数据类型或String类型
         如:
         FieldVisitor fieldVisitor = classWriter.visitField(ACC_PRIVATE,"name", "Ljava/lang/String;", null, null);
         fieldVisitor.visitAnnotation("Llombok/Getter;", false);
         */
        // 获取生成的class的字节数组
        byte[] byteCode = classWriter.toByteArray();

        File file = new File("D:\\util\\mypro\\src\\main\\java\\classhandle\\"
                +className.substring(className.lastIndexOf(".")+1)+ ".class");
        if ((!file.exists() || file.delete()) && file.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(byteCode);
            }
        }
    }

    /**
     * asm改写类并改写方法
     * 改写一个类首先需要获取到一个类的字节数组。可通过使用ClassReader读取并解析获取到一个类的字节数组,
     * 再将解析后的字节数组交给ClassWriter去改写
     * @throws Exception
     */
    public static void b() throws Exception{
        String className = "classhandle.AsmClass";
        ClassReader classReader = new ClassReader(className);
        ClassWriter classWriter = new ClassWriter(0);
        classReader.accept(classWriter, 0);
        FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PRIVATE,
                "name", "Ljava/lang/String;", null, null);
        fieldVisitor.visitAnnotation("Llombok/Getter;", false);

        byte[] byteCode = classWriter.toByteArray();

        File file = new File("D:\\util\\mypro\\src\\main\\java\\classhandle\\"
                +className.substring(className.lastIndexOf(".")+1)+ ".class");
        if ((!file.exists() || file.delete()) && file.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(byteCode);
            }
        }
    }

    /**
     * 实现接口
     * @throws Exception
     */
    public static void c() throws Exception{
        // 创建的类的类名
        String implClassName = BaseByteCodeHandler.class.getName() + "Impl";
        ClassWriter cw = new ClassWriter(0);
        // 设置class文件结构的版本号、类名、类签名、父类、实现的接口
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
                implClassName.replace(".", "/"),
                null,
                Type.getInternalName(Object.class),
                new String[]{Type.getInternalName(BaseByteCodeHandler.class)});
        // 创建asyHello方法
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "order",
                "()V", null, null);
        // 插入输出"hello word!"的字节码指令
        mv.visitFieldInsn(Opcodes.GETSTATIC,
                Type.getInternalName(System.class),
                "out",
                Type.getDescriptor(System.out.getClass()));
        mv.visitLdcInsn("hello word!");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                Type.getInternalName(System.out.getClass()),
                "println",
                "(Ljava/lang/String;)V", false);
        // void方法也需要有return指令
        mv.visitInsn(Opcodes.RETURN);
        // 设置局部变量表和操作数栈的大小
        mv.visitMaxs(2,1);
        // 获取生成的类的字节数组
        byte[] byteCode = cw.toByteArray();

    }

    /**
     * 创建子类重写父类方法
     * @throws Exception
     */
    public static void d() throws Exception{
        // 创建的类的类名
        String subClassName = AsmTest.class.getName()
                +"Sub";
        ClassWriter cw = new ClassWriter(0);

        // 设置class文件结构的版本号、类名、类签名、父类、实现的接口
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
                subClassName.replace(".", "/"),
                null,
                Type.getInternalName(AsmTest.class),
                null);

        // 创建方法
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "d",
                "()V", null, null);

        // 调用父类的方法
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
                Type.getInternalName(AsmTest.class),
                "d",
                "()V", false);

        // 插入输出"SubClass sayHello"的字节码指令
        mv.visitFieldInsn(Opcodes.GETSTATIC,
                Type.getInternalName(System.class),
                "out",
                Type.getDescriptor(System.out.getClass()));
        mv.visitLdcInsn("SubClass sayHello");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                Type.getInternalName(System.out.getClass()),
                "println",
                "(Ljava/lang/String;)V", false);

        mv.visitInsn(Opcodes.RETURN);
        // 设置局部变量表和操作数栈的大小
        mv.visitMaxs(2, 1);

        // 获取生成的类的字节数组
        byte[] byteCode = cw.toByteArray();

    }

}


版权声明:本文为wste3567原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。