ASM depth of the underlying open source framework

What is ASM?

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate the class or classes of existing enhancements. ASM can have a direct binary class files can also be loaded before the Java Virtual Machine dynamically change class behavior in class. Java class is strictly in the format defined in the .class file, class files have metadata store sufficient to resolve all the elements class: class names, methods, properties, and Java byte code (instruction). After the class information from the ASM file is read, it is possible to alter the behavior analysis of class information, a new class can be generated even according to user requirements.

Why dynamically generated Java classes?

Imagine, if you add a variety of open-source framework requires Java class to implement functions such as log, cache, transaction and so on, I think this open source framework you would not use it. Dynamically generated class can reduce intrusion into your code, improve the efficiency of the user.

Why ASM?

The most direct way to transform Java class than the direct overwrite class files. Java specification details the format of class files, directly edit the bytecode can really change the behavior of Java classes. Even today, there are a number of Java experts who use the most primitive tools, such as UltraEdit editor surgery for class files. Yes, this is the most direct method, but requires the user of the Java class file format cooked in mind: carefully calculate the offset of the header function of the relative file you want to transform, but recalculated checksum class file through security mechanisms of the Java virtual machine.

Can be found directly manipulate class file is too much trouble, just why we have chosen to use the same frame, frame shields the complexity of the underlying. ASM is the operating class of a weapon.

Use ASM Programming

ASM provides two API:

  1. CoreAPI(ClassVisitor 、MethodVisitor等)
  2. TreeAPI(ClassNode,MethodNode等)

The difference is CoreAPI event-based model, each element of the definition of the Visitor Class there is no need to load the entire Class into memory. TreeAPI to the entire structure Class Tree structure read into memory. From the point of view TreeAPI easier to use.

The following example uses CoreAPI manner.

Add Maven:

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>5.0.4</version>
</dependency>
复制代码

Use is relatively stable, more use version 5.0.4.

First explain, there are many ways to modify Class, for example, directly modify the current Class, or Class of subclassing, so as to achieve an enhanced effect.

The following example is designated by subclassing Class to achieve the effect of enhancing the benefits of the original Class is non-invasive, and can achieve the effect of polymorphism.

First, we define a class to be enhanced:

package com.zjz;

import java.util.Random;

/**
 * @author zhaojz created at 2019-08-22 10:49
 */
public class Student {
    public String name;

    public void studying() throws InterruptedException {
        System.out.println(this.name+"正在学习...");
        Thread.sleep(new Random().nextInt(5000));
    }
}

复制代码

Next First define a ClassReader:

ClassReader classReader = new ClassReader("com.zjz.Student");
复制代码

And then define a ClassWriter:

 ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
复制代码

ClassWriter.COMPUTE_MAXS represents automatically calculates operand stack and local variable size. More may refer to other options: asm.ow2.io

Then began an official visit to Class:

//通过ClassVisitor访问Class(匿名类的方式,可以自行定义为一个独立的类)
//ASM5为JVM字节码指令操作码
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
	//声明一个全局变量,表示增强后生成的子类的父类
   String enhancedSuperName;
   @Override
   public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   //拼接需要生成的子类的类名:Student$EnhancedByASM
   String enhancedName = name+"$EnhancedByASM";
   //将Student设置为父类
   enhancedSuperName = name;
   super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces);
   }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
    //这里是演示字段访问
    System.out.println("Field:" + name);
    return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    System.out.println("Method:" + name);
    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
    MethodVisitor wrappedMv = mv;
    //判断当前读取的方法
    if (name.equals("studying")) {
    //如果是studying方法,则包装一个方法的Visitor
    wrappedMv = new StudentStudyingMethodVisitor(Opcodes.ASM5, mv);
    }else if(name.equals("<init>")){
    //如果是构造方法,处理子类中父类的构造函数调用
    wrappedMv = new StudentEnhancedConstructorMethodVisitor(Opcodes.ASM5, mv,enhancedSuperName);
    }
    return wrappedMv;
    }
};
复制代码

Next, look at the focus MethodVisitor:

//Studying方法的Visitor
static class StudentStudyingMethodVisitor extends MethodVisitor{

    public StudentStudyingMethodVisitor(int i, MethodVisitor methodVisitor) {
    	super(i, methodVisitor);
    }

	//MethodVisitor 中定义了不同的visitXXX()方法,代表的不同的访问阶段。
	//visitCode表示刚刚进入方法。
    @Override
    public void visitCode() {
    	//添加一行System.currentTimeMillis()调用
        visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        //并且将其存储在局部变量表内位置为1的地方
        visitVarInsn(Opcodes.LSTORE, 1);
        //上面两个的作用就是在Studying方法的第一行添加 long start = System.currentTimeMillis()
    }

	//visitInsn 表示访问进入了方法内部
    @Override
    public void visitInsn(int opcode) {
    	//通过opcode可以得知当前访问到了哪一步,如果是>=Opcodes.IRETURN && opcode <= Opcodes.RETURN 表明方法即将退出
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)){
        	//加载局部变量表中位置为1的数据,也就是start的数据,并传入给下面的方法
            visitVarInsn(Opcodes.LLOAD, 1);
            //然后调用自定义的一个工具方法,用来输出耗时
            visitMethodInsn(Opcodes.INVOKESTATIC, "com/zjz/Before", "end", "(J)V", false);
        }
        super.visitInsn(opcode);
    }


}

static class StudentEnhancedConstructorMethodVisitor extends MethodVisitor{
	//定义一个全局变量记录父类名称
    private String superClassName;
    public StudentEnhancedConstructorMethodVisitor(int i, MethodVisitor methodVisitor,String superClassName) {
        super(i, methodVisitor);
        this.superClassName = superClassName;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean b) {
    	//当开始初始化构造函数时,先访问父类构造函数,类似源码中的super()
        if (opcode==Opcodes.INVOKESPECIAL && name.equals("<init>")){
        	owner = superClassName;
        }
        super.visitMethodInsn(opcode, owner, name, desc, b);
    }
}
复制代码

In this case the input data has not ClassVisitor, only defines new output data ClassVisitor (Opcodes.ASM5, classWriter), so it needs:

classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
复制代码

This completes the reading of the Class, visit modification process, output.

Attentive viewers will find, and where output to? How to access the new generated classes do? So we need to define a Class ClassLoader to load our generation:

 static class StudentClassLoader extends ClassLoader{
        public Class defineClassFromClassFile(String className,byte[] classFile) throws ClassFormatError{
            return defineClass(className, classFile, 0, classFile.length);
        }
    }
复制代码

Then acquires the newly generated class byte array by ClassWriter, and loaded into the JVM:

 byte[] data = classWriter.toByteArray();
 Class subStudent = classLoader.defineClassFromClassFile("com.zjz.Student$EnhancedByASM", data);
复制代码

This completes the generation of a class, the above code completion is a very simple thing: recording learning time.

in conclusion:

ASM CoreAPI core of three things is ClassReader, Visitor, ClassWriter, through the chain of responsibility pattern will link them together.

Visitor access methods, fields, and so on through the attributes of the visitor pattern, if a need to modify the methods and fields and need to be original to the Visitor Wrap click.

hook on the need to understand how the code associated JVM byte code instructions, and the associated OpCode ASM.

ASM Bytecode Outline 2017

But so much instruction, OpCode, symbol remember how to live it? Such as the above code:

visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
visitVarInsn(Opcodes.LSTORE, 1);
复制代码

Opcodes.INVOKESTATIC, Opcodes.LSTORE, () J, is not looking dizzy? In fact, in addition to practice makes perfect, you can also use the tool.

If you are using IDEA, you can install plug-ins on ASM Bytecode Outline 2017. Right then select Show Bytecode Outline on the source file, you will see the following view:

image.png

ASMified view switching, you'll see the same with the code we wrote above, you can use directly Copy over.

View complete source code examples: asm_demo

References:

asm.ow2.io/

www.ibm.com/developerwo…

juejin.im/post/5b549b…

Guess you like

Origin juejin.im/post/5d6f423ae51d4561df7805f7