ASM bytecode operation class library (opening the door from the Java language world to the bytecode world) | JD Cloud Technology Team

Preface: Teaching a man to fish is better than teaching him to fish . There are many articles about using ASM, and there are also many simple demos. So what capabilities does ASM have? How to learn to write ASM code? What kind of scenarios require the use of ASM? Let’s read this article with these questions in mind.

This section has been deleted due to space limitations (Part 6: Comparison of TreeApi and CoreApi, introduction to core API classes, etc.). If you are interested, you can contact the author for communication.

Personally, I think the core lies in the fifth part, how to check how to write the ASM code of a class you want to write, and fully understand the capabilities of ASM, so that in specific scenarios later, we will know that we can use it to achieve the functions we want to do.

1. Introduction to ASM

1. What is ASM?

ASM is a general-purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or directly generate classes dynamically in binary form. ASM provides a number of common bytecode transformation and analysis algorithms from which custom complex transformation and code analysis tools can be built. ASM provides similar functionality to other Java bytecode frameworks, but focuses on performance. Since it is designed and implemented to be as small and fast as possible, it is well suited for use in dynamic systems (but of course can also be used in a static way, such as in a compiler).

A .java file will generate a .class file after being compiled by the Java compiler (javac). In the .class file, bytecode (ByteCode) data is stored. The object operated by ASM is bytecode (ByteCode), and in many cases, the specific form of bytecode (ByteCode) is a .class file.

The way ASM processes bytecode (ByteCode) is "split-modify-merge".

Bytecode tools Class creation implement interface method call class extension Parent class method call advantage shortcoming Common uses learning cost
java-proxy support support support not support not support Simple dynamic proxy preferred Limited functionality and no extension support spring-aop,MyBatis 1 star
asm support support support support support Arbitrary bytecode insertion with almost no restrictions Difficult to learn and a lot of coding to do cglib 5 stars
Javanese support support support support support Java original syntax, string form insertion, intuitive writing Does not support jdk1.5 or above syntax, such as generics, enhanced for Fastjson, MyBatis 2 stars
cglib support support support support support Looks similar to bytebuddy Being phased out by bytebuddy EasyMock,jackson-databind 3 stars
bytebuddy support support support support support Supports interception in any dimension, and can obtain original classes, methods, proxy classes and all parameters Not very intuitive, requires some learning and understanding, and has a lot of APIs SkyWalking,Mockito,Hibernate,powermock 3 stars

Comparison table reference: http://xingyun.jd.com/shendeng/article/detail/7826

  • ASM official website: https://asm.ow2.io/
  • ASM source code: https://gitlab.ow2.org/asm/asm
  • Developer Guide: https://asm.ow2.io/developer-guide.html

2. What can ASM do?

Generate, modify, delete (interfaces, classes, fields, methods...) ASM can analyze, generate, and transform bytecode data. ASM can be vividly understood as a door on the edge of the "Java language world". Through this Opening the door can help us enter the "world of bytecode".

3. ASM actual usage scenarios

3.1. ASM in Spring

The first application scenario is AOP in the Spring framework. In many Java projects, the Spring framework is used, and AOP (Aspect Oriented Programming) in the Spring framework relies on ASM. Specifically, Spring's AOP can be implemented through the JDK's dynamic proxy or through CGLIB. Among them, CGLib (Code Generation Library) is built on the basis of ASM, so Spring AOP uses ASM indirectly. (Referenced from 8.6 Proxying mechanisms of Spring Framework Reference Documentation).

3.2. ASM in JDK

The second application scenario is Lambda expression in JDK. A very important feature introduced in Java 8 is support for Lambda expressions. Lambda expressions allow methods to be passed as parameters, which can make the code more concise and compact. However, we may not have noticed that, in fact, at this stage (Java 8 version), the calling of Lambda expressions is implemented through ASM.

The jdk.internal.org.objectweb.asm package in the rt.jar file contains the JDK's built-in ASM code. In JDK 8 version, it uses ASM 5.0 version.

If we trace the coding implementation of the Lambda expression, we will find the InnerClassLambdaMetafactory.spinInnerClass() method. In this method, we will see: JDK will use jdk.internal.org.objectweb.asm.ClassWriter to generate a class to wrap the code of the lambda expression.

LambdaMetafactory.metafactory() The first step is to find this method. InnerClassLambdaMetafactory.buildCallSite() The second step is to find this method.

​ InnerClassLambdaMetafactory.spinInnerClass() The third step is to find this method

4. Two components of ASM

In terms of composition structure, ASM is divided into two parts, one is the Core API and the other is the Tree API.

Among them, Core API includes asm.jar, asm-util.jar and asm-commons.jar; among them, Tree API includes asm-tree.jar and asm-analysis.jar.

asm.jar内核心类:ClassReader、ClassVisitor、ClassWriter、FieldVisitor、FieldWriter、MethodVisitor、MethodWriter、Label、Opcodes、Type

The ClassReader class is responsible for reading the contents of the .class file and then splitting it into different parts. The ClassVisitor class is responsible for modifying the information in a certain part of the .class file. The ClassWriter class is responsible for reassembling the different parts into a complete .class file.

Core classes in asm-util.jar

Classes starting with Check are mainly responsible for checking (Check) whether the content of the generated .class file is correct. Classes starting with Trace are mainly responsible for printing the contents of the .class file into text output. Based on the output text information, the internal information of the .class file can be explored or traced (Trace).

5、ClassFile

We all know that in the .class file, ByteCode data is stored. However, these ByteCode data are not messy, but follow a certain data structure.

The data structure that this .class file follows is The class File Format defined in the Java Virtual Machine Specification.

6. Common bytecode libraries

Apache Commons BCEL: BCEL is the abbreviation of Byte Code Engineering Library.

Javassist:Javassist表示Java programming assistant

ObjectWeb ASM: The subject of this course.

Byte Buddy: A class library implemented based on ASM.

2. Create something out of nothing

1. Generate a new interface

Expected goals:

Generate a .class file defining a normal interface structure

public interface ASMInterface {
    byte byteType = 1;
    short shortType = 1;
    int intType = 1;
    char charType = 's';
    float floatType = 1.1F;
    double doubleType = 1.2;
    long longType = 1L;
    boolean booleanType = false;
    Byte ByteType = 1;
    Short ShortType = Short.valueOf((short)1);
    Integer IntegerType = 1;
    String StringType = "s";
    Float FloatType = 1.1F;
    Double DoubleType = 1.1;
    Long LongType = 1L;
    Boolean BooleanType = true;

    void function();

    default String defaultFunction(Integer integer) {
        System.out.println("param = " + integer);
        return String.valueOf(integer);
    }

    static Integer getInteger(String str) {
        return Integer.valueOf(str);
    }
}

Coding implementation:

public class InterfaceGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/ASMGenerateInterface.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法,调用顺序和说明如下
        /*
         *  visit
         *  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
         *  (visitAnnotation |
         *   visitTypeAnnotation |
         *   visitAttribute)*
         *  (visitNestMember |
         *   visitInnerClass |
         *   visitRecordComponent |
         *   visitField |
         *   visitMethod)*
         *  visitEnd
         *  []: 表示最多调用一次,可以不调用,但最多调用一次。
         *  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
         *  *: 表示方法可以调用0次或多次。
         * */

        //定义接口
        /*
         *visit(version, access, name, signature, superName, interfaces)
         *version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。
         *access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。
         *name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。
         *signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。
         *superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。
         *interfaces: 表示当前类实现了哪些接口信息。
         **/
        cw.visit(
                V1_8,                                        // version
                ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,   // access
                "sample/ASMGenerateInterface",               // name
                null,                                        // signature
                "java/lang/Object",                          // superName
                null                                         // interfaces
        );
        //定义字段-基本类型
        /*
         * visitField(access, name, descriptor, signature, value)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。
         * */
        {
            FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "byteType", "B", null, new Integer(1));
            fv1.visitEnd();
        }
        {
            FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "shortType", "S", null, new Integer(1));
            fv2.visitEnd();
        }
        {
            FieldVisitor fv3 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "intType", "I", null, new Integer(1));
            fv3.visitEnd();
        }
        {
            FieldVisitor fv4 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "charType", "C", null, 's');
            fv4.visitEnd();
        }
        {
            FieldVisitor fv5 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "floatType", "F", null, new Float("1.1"));
            fv5.visitEnd();
        }
        {
            FieldVisitor fv6 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "doubleType", "D", null, new Double("1.2"));
            fv6.visitEnd();
        }
        {
            FieldVisitor fv7 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "longType", "J", null, new Long(1L));
            fv7.visitEnd();
        }
        {
            FieldVisitor fv8 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "booleanType", "Z", null, new Integer(0));
            fv8.visitEnd();
        }
        //定义变量-包装类型
        {
            FieldVisitor fv11 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ByteType", "Ljava/lang/Byte;", null, null);
            fv11.visitEnd();
        }
        {
            FieldVisitor fv12 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ShortType", "Ljava/lang/Short;", null,null);
            fv12.visitEnd();
        }
        {
            FieldVisitor fv13 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "IntegerType", "Ljava/lang/Integer;", null,null);
            fv13.visitEnd();
        }
        {
            FieldVisitor fv14 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "StringType", "Ljava/lang/String;", null, "s");
            fv14.visitEnd();
        }
        {
            FieldVisitor fv15 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "FloatType", "Ljava/lang/Float;", null,null);
            fv15.visitEnd();
        }
        {
            FieldVisitor fv16 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "DoubleType", "Ljava/lang/Double;", null,null);
            fv16.visitEnd();
        }
        {
            FieldVisitor fv17 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "LongType", "Ljava/lang/Long;", null,  null);
            fv17.visitEnd();
        }
        {
            FieldVisitor fv18 = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "BooleanType", "Ljava/lang/Boolean;", null, null);
            fv18.visitEnd();
        }
        //定义方法-抽象方法
        /*
         * visitMethod(access, name, descriptor, signature, exceptions)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。
         * */
        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "function", "()V", null, null);
            mv1.visitEnd();
        }
        //定义方法-默认方法
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "defaultFunction", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv2.visitInsn(DUP);
            mv2.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv2.visitLdcInsn("param = ");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(3, 2);
            mv2.visitEnd();
        }
        //定义方法-静态方法
        {
            MethodVisitor mv3 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);
            mv3.visitCode();
            mv3.visitVarInsn(ALOAD, 0);
            mv3.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);
            mv3.visitInsn(ARETURN);
            mv3.visitMaxs(1, 1);
            mv3.visitEnd();
        }

        {
            MethodVisitor mv4 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv4.visitCode();
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ByteType", "Ljava/lang/Byte;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "ShortType", "Ljava/lang/Short;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "IntegerType", "Ljava/lang/Integer;");
            mv4.visitLdcInsn(new Float("1.1"));
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "FloatType", "Ljava/lang/Float;");
            mv4.visitLdcInsn(new Double("1.1"));
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "DoubleType", "Ljava/lang/Double;");
            mv4.visitInsn(LCONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "LongType", "Ljava/lang/Long;");
            mv4.visitInsn(ICONST_1);
            mv4.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            mv4.visitFieldInsn(PUTSTATIC, "sample/ASMInterface", "BooleanType", "Ljava/lang/Boolean;");
            mv4.visitInsn(RETURN);
            mv4.visitMaxs(2, 0);
            mv4.visitEnd();
        }
        cw.visitEnd(); // 注意,最后要调用visitEnd()方法
        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results:

Is the generated interface correct?

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = classLoader.loadClass("sample.ASMGenerateInterface");
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields.length > 0) {
            System.out.println("fields:");
            for (Field f : declaredFields) {
                Object value = f.get(null);
                System.out.println("    " + f.getName() + ": " + value);
            }
        }
        Method[] declaredMethods = clazz.getDeclaredMethods();
        if (declaredMethods.length > 0) {
            System.out.println("methods:");
            for (Method m : declaredMethods) {
                System.out.println("    " + m.getName());
            }
        }
    }
}

The renderings are as follows:



2. Generate new classes

Expected goals:

Generate a .class file defining a normal class structure

public class ASMClass {
    //定义变量-基本类型
    byte byteType = 1;
    short shortType = 1;
    int intType = 1;
    char charType = 's';
    float floatType = 1.1f;
    double doubleType = 1.2;
    long longType = 1;
    boolean booleanType = false;
    //定义变量-包装类型
    Byte ByteType = 1;
    Short ShortType = 1;
    Integer IntegerType = 1;
    String StringType = "string";
    Float FloatType = 1.1f;
    Double DoubleType = 1.1;
    Long LongType = 1l;
    @Deprecated
    Boolean BooleanType = true;
    /*
     * 静态方法
     * */
    public static Integer getInteger(String str) {
        return Integer.valueOf(str);
    }
    /*
     * 实例方法
     * */
    public String instanceMethod(Integer integer) {
        return String.valueOf(integer);
    }
}

Coding implementation:

public class ClassGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/ASMGenerateClass.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法,调用顺序和说明如下
        /*
         *  visit
         *  [visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
         *  (visitAnnotation |
         *   visitTypeAnnotation |
         *   visitAttribute)*
         *  (visitNestMember |
         *   visitInnerClass |
         *   visitRecordComponent |
         *   visitField |
         *   visitMethod)*
         *  visitEnd
         *  []: 表示最多调用一次,可以不调用,但最多调用一次。
         *  ()和|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
         *  *: 表示方法可以调用0次或多次。
         * */

        //定义接口
        /*
         *visit(version, access, name, signature, superName, interfaces)
         *version: 表示当前类的版本信息。在下述示例代码中,其取值为Opcodes.V1_8,表示使用Java 8版本。
         *access: 表示当前类的访问标识(access flag)信息。在下面的示例中,access的取值是ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,也可以写成ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE。如果想进一步了解这些标识的含义,可以参考Java Virtual Machine Specification的Chapter 4. The class File Format部分。
         *name: 表示当前类的名字,它采用的格式是Internal Name的形式。在.java文件中,我们使用Java语言来编写代码,使用类名的形式是Fully Qualified Class Name,例如java.lang.String;将.java文件编译之后,就会生成.class文件;在.class文件中,类名的形式会发生变化,称之为Internal Name,例如java/lang/String。因此,将Fully Qualified Class Name转换成Internal Name的方式就是,将.字符转换成/字符。
         *signature: 表示当前类的泛型信息。因为在这个接口当中不包含任何的泛型信息,因此它的值为null。
         *superName: 表示当前类的父类信息,它采用的格式是Internal Name的形式。
         *interfaces: 表示当前类实现了哪些接口信息。
         **/
        cw.visit(
                V1_8,                                        // version
                ACC_PUBLIC + ACC_SUPER,   // access
                "sample/ASMGenerateClass",               // name
                null,                                        // signature
                "java/lang/Object",                          // superName
                null                                         // interfaces
        );
        //定义字段-基本类型
        /*
         * visitField(access, name, descriptor, signature, value)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。byte-B、short-S、int-I、char-C、具体可以参考如下示例代码
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *value参数:是visitField()方法的第5个参数。这个参数的取值,与当前字段是否为常量有关系。如果当前字段是一个常量,就需要给value参数提供某一个具体的值;如果当前字段不是常量,那么使用null就可以了。
         * */
        {
            FieldVisitor fv1 = cw.visitField(0, "byteType", "B", null, new Byte("1"));
            fv1.visitEnd();
        }
        {
            FieldVisitor fv2 = cw.visitField(0, "shortType", "S", null, new Short("1"));
            fv2.visitEnd();
        }
        {
            FieldVisitor fv3 = cw.visitField(0, "intType", "I", null, new Integer(1));
            fv3.visitEnd();
        }
        {
            FieldVisitor fv4 = cw.visitField(0, "charType", "C", null, "s");
            fv4.visitEnd();
        }
        {
            FieldVisitor fv5 = cw.visitField(0, "floatType", "F", null, new Float("1.1"));
            fv5.visitEnd();
        }
        {
            FieldVisitor fv6 = cw.visitField(0, "doubleType", "D", null, new Double("1.2"));
            fv6.visitEnd();
        }
        {
            FieldVisitor fv7 = cw.visitField(0, "longType", "J", null, new Long(1));
            fv7.visitEnd();
        }
        {
            FieldVisitor fv8 = cw.visitField(0, "booleanType", "Z", null, false);
            fv8.visitEnd();
        }
        //定义变量-包装类型
        {
            FieldVisitor fv11 = cw.visitField(0, "ByteType", "Ljava/lang/Byte;", null, 1);
            fv11.visitEnd();
        }
        {
            FieldVisitor fv12 = cw.visitField(0, "ShortType", "Ljava/lang/Short;", null, 1);
            fv12.visitEnd();
        }
        {
            FieldVisitor fv13 = cw.visitField(0, "IntegerType", "Ljava/lang/Integer;", null, 1);
            fv13.visitEnd();
        }
        {
            FieldVisitor fv14 = cw.visitField(0, "StringType", "Ljava/lang/String;", null, "s");
            fv14.visitEnd();
        }
        {
            FieldVisitor fv15 = cw.visitField(0, "FloatType", "Ljava/lang/Float;", null, 1.1f);
            fv15.visitEnd();
        }
        {
            FieldVisitor fv16 = cw.visitField(ACC_PUBLIC, "DoubleType", "Ljava/lang/Double;", null, 1.1);
            fv16.visitEnd();
        }
        {
            FieldVisitor fv17 = cw.visitField(0, "LongType", "Ljava/lang/Long;", null, 1l);
            fv17.visitEnd();
        }
        {
            FieldVisitor fv18 = cw.visitField(ACC_DEPRECATED, "BooleanType", "Ljava/lang/Boolean;", null, true);
            {
                AnnotationVisitor annotationVisitor0 = fv18.visitAnnotation("Ljava/lang/Deprecated;", true);
                annotationVisitor0.visitEnd();
            }
            fv18.visitEnd();
        }
        /*
         * visitMethod(access, name, descriptor, signature, exceptions)
         *access参数:表示当前字段或方法带有的访问标识(access flag)信息,例如ACC_PUBLIC、ACC_STATIC和ACC_FINAL等。
         *name参数:表示当前字段或方法的名字。
         *descriptor参数:表示当前字段或方法的描述符。这些描述符,与我们平时使用的Java类型是有区别的。()内为入参,后面为反参
         *signature参数:表示当前字段或方法是否带有泛型信息。换句话说,如果不带有泛型信息,提供一个null就可以了;如果带有泛型信息,就需要给它提供某一个具体的值。
         *exceptions参数:是visitMethod()方法的第5个参数。这个参数的取值,与当前方法声明中是否具有throws XxxException相关。
         * */
        //定义方法-静态代码块
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv2.visitCode();
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("class initialization method");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 0);
            mv2.visitEnd();
        }
        //定义方法-无参构造器
        {
            MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "byteType", "B");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "shortType", "S");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "intType", "I");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitIntInsn(BIPUSH, 115);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "charType", "C");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Float("1.1"));
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "floatType", "F");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Double("1.2"));
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "doubleType", "D");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(LCONST_1);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "longType", "J");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_0);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "booleanType", "Z");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ByteType", "Ljava/lang/Byte;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "ShortType", "Ljava/lang/Short;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "IntegerType", "Ljava/lang/Integer;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn("string");
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "StringType", "Ljava/lang/String;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Float("1.1"));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "FloatType", "Ljava/lang/Float;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitLdcInsn(new Double("1.1"));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "DoubleType", "Ljava/lang/Double;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(LCONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "LongType", "Ljava/lang/Long;");
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            methodVisitor.visitFieldInsn(PUTFIELD, "sample/ASMGenerateClass", "BooleanType", "Ljava/lang/Boolean;");
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(3, 1);
            methodVisitor.visitEnd();

        }
        //定义方法-静态方法
        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getInteger", "(Ljava/lang/String;)Ljava/lang/Integer;", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;", false);
            mv1.visitInsn(ARETURN);
            mv1.visitMaxs(1, 1);
            mv1.visitEnd();
        }
        //定义方法-实例方法
        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "instanceMethod", "(Ljava/lang/Integer;)Ljava/lang/String;", null, null);
            mv2.visitCode();
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;", false);
            mv2.visitInsn(ARETURN);
            mv2.visitMaxs(1, 2);
            mv2.visitEnd();
        }

        cw.visitEnd(); // 注意,最后要调用visitEnd()方法
        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results:

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.ASMGenerateClass");
        Method method = clazz.getDeclaredMethod("instanceMethod",Integer.class);
        Object instance = clazz.newInstance();
        Object invoke = method.invoke(instance, new Integer(12));
        Class<?> aClass = invoke.getClass();
        System.out.println("aClass = " + aClass);
    }
}
或者
public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = classLoader.loadClass("sample.ASMGenerateClass");
        Field[] declaredFields = clazz.getDeclaredFields();
        if (declaredFields.length > 0) {
            for (Field f : declaredFields) {
                Object value = f.get(null);
                System.out.println("    " + f.getName() + ": " + value);
            }
        }
        Method[] declaredMethods = clazz.getDeclaredMethods();
        if (declaredMethods.length > 0) {
            for (Method m : declaredMethods) {
                System.out.println("    " + m.getName());
            }
        }
    }
}

The renderings are as follows:

The calling sequence of visitXxx() in ClassVisitor

visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
(
 visitNestMember |
 visitInnerClass |
 visitRecordComponent |
 visitField |
 visitMethod
)* 
visitEnd
其中,涉及到一些符号,它们的含义如下:
[]: 表示最多调用一次,可以不调用,但最多调用一次。
()|: 表示在多个方法之间,可以选择任意一个,并且多个方法之间不分前后顺序。
*: 表示方法可以调用0次或多次。

The calling sequence of visitXxx() in FieldVisitor

(
 visitAnnotation |
 visitTypeAnnotation |
 visitAttribute
)*
visitEnd

The calling sequence of visitXxx() in MethodVisitor

(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
    visitCode
    (
        visitFrame |
        visitXxxInsn |
        visitLabel |
        visitInsnAnnotation |
        visitTryCatchBlock |
        visitTryCatchAnnotation |
        visitLocalVariable |
        visitLocalVariableAnnotation |
        visitLineNumber
    )*
    visitMaxs
]
visitEnd
第一组,在visitCode()方法之前的方法。这一组的方法,主要负责parameter、annotation和attributes等内容
第二组,在visitCode()方法和visitMaxs()方法之间的方法。这一组的方法,主要负责当前方法的“方法体”内的opcode内容。其中,visitCode()方法,标志着方法体的开始,而visitMaxs()方法,标志着方法体的结束。
第三组,是visitEnd()方法。这个visitEnd()方法,是最后一个进行调用的方法。

The visitXxx() methods of different MethodVisitor objects are independent of each other. As long as they follow the calling order of the methods, they can get the correct results.

3. The civet cat exchanges for the prince

1. Modify the version of the class

ClassVisitor subclass implementation

public class ClassChangeVersionVisitor extends ClassVisitor {
    public ClassChangeVersionVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(Opcodes.V1_7, access, name, signature, superName, interfaces);
    }
}

Use ClassChangeVersionVisitor, a subclass of ClassVisitor, to modify the class version

public class ASMModifyClass {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new ClassChangeVersionVisitor(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

Verification & Effect

Through the javap -p -v HelloWorld command, you can see that the version number information has been adjusted from 52 to 51



2. Add calculation call time to each method

Modify and exchange the methods of the target class---Add time calculation for each method


public class HelloWorld {
    public int add(int a, int b) throws InterruptedException {
        int c = a + b;
        Random rand = new Random(System.currentTimeMillis());
        int num = rand.nextInt(300);
        Thread.sleep(100 + num);
        return c;
    }

    public int sub(int a, int b) throws InterruptedException {
        int c = a - b;
        Random rand = new Random(System.currentTimeMillis());
        int num = rand.nextInt(400);
        Thread.sleep(100 + num);
        return c;
    }
}

ASM coding implementation


public class MethodTimerVisitor2 extends ClassVisitor {
    private String owner;
    private boolean isInterface;

    public MethodTimerVisitor2(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        owner = name;
        isInterface = (access & ACC_INTERFACE) != 0;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (!isInterface && mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                // 每遇到一个合适的方法,就添加一个相应的字段
                FieldVisitor fv = super.visitField(ACC_PUBLIC | ACC_STATIC, getFieldName(name), "J", null, null);
                if (fv != null) {
                    fv.visitEnd();
                }

                mv = new MethodTimerAdapter2(api, mv, owner, name);
            }

        }
        return mv;
    }


    private String getFieldName(String methodName) {
        return "timer_" + methodName;
    }

    private class MethodTimerAdapter2 extends MethodVisitor {
        private final String owner;
        private final String methodName;

        public MethodTimerAdapter2(int api, MethodVisitor mv, String owner, String methodName) {
            super(api, mv);
            this.owner = owner;
            this.methodName = methodName;
        }

        @Override
        public void visitCode() {
            // 首先,处理自己的代码逻辑
            super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
            super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            super.visitInsn(LSUB);
            super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应

            // 其次,调用父类的方法实现
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            // 首先,处理自己的代码逻辑
            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                super.visitFieldInsn(GETSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
                super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                super.visitInsn(LADD);
                super.visitFieldInsn(PUTSTATIC, owner, getFieldName(methodName), "J"); // 注意,字段名字要对应
            }

            // 其次,调用父类的方法实现
            super.visitInsn(opcode);
        }
    }
}

Convert a method


public class HelloWorldTransformCore {
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
  
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String dir = HelloWorldTransformCore.class.getResource("/").getPath();
        String filepath = dir + relative_path;
        File file = new File(filepath);
        try {
            InputStream in = new FileInputStream(file);
            in = new BufferedInputStream(in);
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            copyLarge(in, bao, new byte[DEFAULT_BUFFER_SIZE]);
            byte[] bytes1 = bao.toByteArray();

            //(1)构建ClassReader
            ClassReader cr = new ClassReader(bytes1);

            //(2)构建ClassWriter
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

            //(3)串连ClassVisitor
            int api = Opcodes.ASM9;
            ClassVisitor cv = new MethodTimerVisitor2(api, cw);

            //(4)结合ClassReader和ClassVisitor
            int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
            cr.accept(cv, parsingOptions);

            //(5)生成byte[]
            byte[] bytes2 = cw.toByteArray();
            OutputStream out = new FileOutputStream(filepath);
            BufferedOutputStream buff = new BufferedOutputStream(out);
            buff.write(bytes2);
            buff.flush();
            buff.close();
            System.out.println("file://" + filepath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)
            throws IOException {
        long count = 0;
        int n;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }
}

Validation results

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        // 第一部分,先让“子弹飞一会儿”,让程序运行一段时间
        HelloWorld instance = new HelloWorld();
        Random rand = new Random(System.currentTimeMillis());
        for (int i = 0; i < 10; i++) {
            boolean flag = rand.nextBoolean();
            int a = rand.nextInt(50);
            int b = rand.nextInt(50);
            if (flag) {
                int c = instance.add(a, b);
                String line = String.format("%d + %d = %d", a, b, c);
                System.out.println(line);
            }
            else {
                int c = instance.sub(a, b);
                String line = String.format("%d - %d = %d", a, b, c);
                System.out.println(line);
            }
        }

        // 第二部分,来查看方法运行的时间
        Class<?> clazz = HelloWorld.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            String fieldName = f.getName();
            f.setAccessible(true);
            if (fieldName.startsWith("timer")) {
                Object FieldValue = f.get(null);
                System.out.println(fieldName + " = " + FieldValue);
            }
        }
    }
}

3. Print method parameters and return values

Modify the method of the target class---Add printing input parameters and output parameters to each method

public class HelloWorld {
    public int test(String name, int age, long idCard, Object obj) {
        int hashCode = 0;
        hashCode += name.hashCode();
        hashCode += age;
        hashCode += (int) (idCard % Integer.MAX_VALUE);
        hashCode += obj.hashCode();
        return hashCode;
    }
}

The expected goal we want to achieve: print out the "parameter values ​​received by the method" and the "return value of the method".

public class HelloWorld {
    public int test(String name, int age, long idCard, Object obj) {

        int hashCode = 0;
        hashCode += name.hashCode();
        hashCode += age;
        hashCode += (int) (idCard % Integer.MAX_VALUE);
        hashCode += obj.hashCode();
        System.out.println(hashCode);
        return hashCode;
    }
}

The idea of ​​​​implementing this function: when the "method enters", print out the "parameter value received by the method"; when the "method exits", print out the "return value of the method".

首先,我们添加一个ParameterUtils类,在这个类定义了许多print方法,这些print方法可以打印不同类型的数据。

public class ParameterUtils {
    private static final DateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void printValueOnStack(boolean value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(byte value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(char value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(short value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(int value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(float value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(long value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(double value) {
        System.out.println("    " + value);
    }

    public static void printValueOnStack(Object value) {
        if (value == null) {
            System.out.println("    " + value);
        }
        else if (value instanceof String) {
            System.out.println("    " + value);
        }
        else if (value instanceof Date) {
            System.out.println("    " + fm.format(value));
        }
        else if (value instanceof char[]) {
            System.out.println("    " + Arrays.toString((char[])value));
        }
        else {
            System.out.println("    " + value.getClass() + ": " + value.toString());
        }
    }

    public static void printText(String str) {
        System.out.println(str);
    }
}

In the following class, we will use the class to help us print information.MethodParameterVisitor2ParameterUtils

ASM coding implementation


public class MethodParameterVisitor2 extends ClassVisitor {
    public MethodParameterVisitor2(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (mv != null && !name.equals("<init>")) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                mv = new MethodParameterAdapter2(api, mv, access, name, descriptor);
            }
        }
        return mv;
    }

    private static class MethodParameterAdapter2 extends MethodVisitor {
        private final int methodAccess;
        private final String methodName;
        private final String methodDesc;

        public MethodParameterAdapter2(int api, MethodVisitor mv, int methodAccess, String methodName, String methodDesc) {
            super(api, mv);
            this.methodAccess = methodAccess;
            this.methodName = methodName;
            this.methodDesc = methodDesc;
        }

        @Override
        public void visitCode() {
            // 首先,处理自己的代码逻辑
            boolean isStatic = ((methodAccess & ACC_STATIC) != 0);
            int slotIndex = isStatic ? 0 : 1;

            printMessage("Method Enter: " + methodName + methodDesc);

            Type methodType = Type.getMethodType(methodDesc);
            Type[] argumentTypes = methodType.getArgumentTypes();
            for (Type t : argumentTypes) {
                int sort = t.getSort();
                int size = t.getSize();
                String descriptor = t.getDescriptor();
                int opcode = t.getOpcode(ILOAD);
                super.visitVarInsn(opcode, slotIndex);
                if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) {
                    String methodDesc = String.format("(%s)V", descriptor);
                    printValueOnStack(methodDesc);
                }
                else {
                    printValueOnStack("(Ljava/lang/Object;)V");
                }

                slotIndex += size;
            }

            // 其次,调用父类的方法实现
            super.visitCode();
        }

        @Override
        public void visitInsn(int opcode) {
            // 首先,处理自己的代码逻辑
            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                printMessage("Method Exit: " + methodName + methodDesc);
                if (opcode >= IRETURN && opcode <= DRETURN) {
                    Type methodType = Type.getMethodType(methodDesc);
                    Type returnType = methodType.getReturnType();
                    int size = returnType.getSize();
                    String descriptor = returnType.getDescriptor();

                    if (size == 1) {
                        super.visitInsn(DUP);
                    }
                    else {
                        super.visitInsn(DUP2);
                    }
                    String methodDesc = String.format("(%s)V", descriptor);
                    printValueOnStack(methodDesc);
                }
                else if (opcode == ARETURN) {
                    super.visitInsn(DUP);
                    printValueOnStack("(Ljava/lang/Object;)V");
                }
                else if (opcode == RETURN) {
                    printMessage("    return void");
                }
                else {
                    printMessage("    abnormal return");
                }
            }

            // 其次,调用父类的方法实现
            super.visitInsn(opcode);
        }

        private void printMessage(String str) {
            super.visitLdcInsn(str);
            super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printText", "(Ljava/lang/String;)V", false);
        }

        private void printValueOnStack(String descriptor) {
            super.visitMethodInsn(INVOKESTATIC, "sample/ParameterUtils", "printValueOnStack", descriptor, false);
        }
    }
}

Make a conversion


public class HelloWorldTransformCore {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new MethodParameterVisitor2(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

Validation results

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        HelloWorld instance = new HelloWorld();
        int hashCode = instance.test("Tomcat", 10, System.currentTimeMillis(), new Object());
        int remainder = hashCode % 2;

        if (remainder == 0) {
            System.out.println("hashCode is even number.");
        }
        else {
            System.out.println("hashCode is odd number.");
        }
    }
}

4. Non-sequential structure

1. if statement

public class HelloWorld {
    public void test(int value) {
        if (value == 0) {
            System.out.println("value is 0");
        }
        else {
            System.out.println("value is not 0");
        }
    }
}

ASM coding implementation


public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);
            Label elseLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            mv2.visitVarInsn(ILOAD, 1);
            mv2.visitJumpInsn(IFNE, elseLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("value is 0");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第2段
            mv2.visitLabel(elseLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("value is not 0");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第3段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results


public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test", int.class);
        method.invoke(obj, 0);
        method.invoke(obj, 1);
    }
}

2. switch statement

public class HelloWorld {
    public void test(int val) {
        switch (val) {
            case 1:
                System.out.println("val = 1");
                break;
            case 2:
                System.out.println("val = 2");
                break;
            case 3:
                System.out.println("val = 3");
                break;
            case 4:
                System.out.println("val = 4");
                break;
            default:
                System.out.println("val is unknown");
        }
    }
}

ASM coding implementation


public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);
            Label caseLabel1 = new Label();
            Label caseLabel2 = new Label();
            Label caseLabel3 = new Label();
            Label caseLabel4 = new Label();
            Label defaultLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            mv2.visitVarInsn(ILOAD, 1);
            mv2.visitTableSwitchInsn(1, 4, defaultLabel, new Label[]{caseLabel1, caseLabel2, caseLabel3, caseLabel4});

            // 第2段
            mv2.visitLabel(caseLabel1);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 1");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第3段
            mv2.visitLabel(caseLabel2);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 2");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第4段
            mv2.visitLabel(caseLabel3);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 3");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第5段
            mv2.visitLabel(caseLabel4);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val = 4");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第6段
            mv2.visitLabel(defaultLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("val is unknown");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第7段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results

import java.lang.reflect.Method;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test", int.class);
        for (int i = 1; i < 6; i++) {
            method.invoke(obj, i);
        }
    }
}

3. for statement

public class HelloWorld {
    public void test() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

ASM coding implementation

import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            Label conditionLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            methodVisitor.visitCode();
            methodVisitor.visitInsn(ICONST_0);
            methodVisitor.visitVarInsn(ISTORE, 1);

            // 第2段
            methodVisitor.visitLabel(conditionLabel);
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitIntInsn(BIPUSH, 10);
            methodVisitor.visitJumpInsn(IF_ICMPGE, returnLabel);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitVarInsn(ILOAD, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
            methodVisitor.visitIincInsn(1, 1);
            methodVisitor.visitJumpInsn(GOTO, conditionLabel);

            // 第3段
            methodVisitor.visitLabel(returnLabel);
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(0, 0);
            methodVisitor.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test");
        method.invoke(obj);
    }
}

4. try-catch statement

public class HelloWorld {
    public void test() {
        try {
            System.out.println("Before Sleep");
            Thread.sleep(1000);
            System.out.println("After Sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ASM coding implementation

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(0, 0);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            Label startLabel = new Label();
            Label endLabel = new Label();
            Label exceptionHandlerLabel = new Label();
            Label returnLabel = new Label();

            // 第1段
            mv2.visitCode();
            // visitTryCatchBlock可以在这里访问
            mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");

            // 第2段
            mv2.visitLabel(startLabel);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("Before Sleep");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitLdcInsn(new Long(1000L));
            mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitLdcInsn("After Sleep");
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            // 第3段
            mv2.visitLabel(endLabel);
            mv2.visitJumpInsn(GOTO, returnLabel);

            // 第4段
            mv2.visitLabel(exceptionHandlerLabel);
            mv2.visitVarInsn(ASTORE, 1);
            mv2.visitVarInsn(ALOAD, 1);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);

            // 第5段
            mv2.visitLabel(returnLabel);
            mv2.visitInsn(RETURN);

            // 第6段
            // visitTryCatchBlock也可以在这里访问
            // mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");
            mv2.visitMaxs(0, 0);
            mv2.visitEnd();
        }

        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

Validation results

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("sample.HelloWorld");
        Object obj = clazz.newInstance();

        Method method = clazz.getDeclaredMethod("test");
        method.invoke(obj);
    }
}

5. View the ASM code of the class file

1.Print

When we want to learn ASM about a certain class or don’t know how to implement the function we want to implement, we can use the following class to output and view the ASM code

public class ASMPrint {
    public static void main(String[] args) throws IOException {
        // (1) 设置参数
        String className = "sample.HelloWorld";
        int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;
        boolean asmCode = true;

        // (2) 打印结果
        Printer printer = asmCode ? new ASMifier() : new Textifier();
        PrintWriter printWriter = new PrintWriter(System.out, true);
        TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
        new ClassReader(className).accept(traceClassVisitor, parsingOptions);
    }
}

The className value is set to the fully qualified name of the class, which can be a class we wrote ourselves, such as sample.HelloWorld, or a class that comes with the JDK, such as java.lang.Comparable.

The asmCode value is set to true or false. If it is true, the corresponding ASM code can be printed; if it is false, the Instruction corresponding to the method can be printed.

The parsingOptions value is set to a combination of ClassReader.SKIP_CODE, ClassReader.SKIP_DEBUG, ClassReader.SKIP_FRAMES, and ClassReader.EXPAND_FRAMES. It can also be set to 0 to print out information with different levels of detail.

2.Plug-in

If you are IDEA, you can install ASM ByteCode Viewer, then select the java file you want to view, right-click and select, and you can see the corresponding ByteCode, ASMified, and Groovified on the ASPPlugin on the right. Or the plug-in ASM Bytecode Outline (the community version of IDEA I used did not achieve the effect)

6. TreeApi

The difference between Core API and Tree API

  • Advantages of Tree API: Ease of use: If a person has not been exposed to Core API and Tree API before, Tree API is easier to get started. Functionality: When implementing more complex functions, Tree API is easier to implement than Core API.
  • Advantages of Core API: Execution efficiency: On the premise of realizing the same function, Core API has higher execution efficiency and takes less time than Tree API. Memory usage: Core API takes up less memory space than Tree API.
  • The first point is that in ASM, whether it is Core API or Tree API, Class Generation, Class Transformation and Class Analysis operations can be performed.
  • The second point is that Core API and Tree API have their own advantages. Tree API is easy to use and easier to implement complex operations; Core API executes faster and takes up less memory space.

This article has been deleted due to space limitations. If you are interested, please contact the author for communication.

7. Tools used in this article

1、FileUtils

public class FileUtils {
    public static String getFilePath(String relativePath) {
        String dir = FileUtils.class.getResource("/").getPath();
        return dir + relativePath;
    }
}

8. Think about what we can do with ASM in the future?

1. Generate classes----generate classes based on templates (combined with scaffolding to quickly build projects and early project modules to quickly build)

2. Modify the class - modify the class according to the template (combine the specific structure to modify the class when compiling the java source code to generate the .class bytecode file to achieve the purpose of improving system efficiency and releasing development manpower. It can be combined with IDEA plug-in development Develop plug-ins similar to lombok or more powerful)

Author: JD Health Ma Renxi

Source: JD Cloud Developer Community Please indicate the source when reprinting

Microsoft launches new "Windows App" Xiaomi officially announces that Xiaomi Vela is fully open source, and the underlying kernel is NuttX Vite 5. Alibaba Cloud 11.12 is officially released. The cause of the failure is exposed: Access Key service (Access Key) anomaly. GitHub report: TypeScript replaces Java and becomes the third most popular. The language operator’s miraculous operation: disconnecting the network in the background, deactivating broadband accounts, forcing users to change optical modems ByteDance: using AI to automatically tune Linux kernel parameters Microsoft open source Terminal Chat Spring Framework 6.1 officially GA OpenAI former CEO and president Sam Altman & Greg Brockman joins Microsoft
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10149739