Architect's Way: ASM Implementing Simple AOP

Posted by mwilson2 on Mon, 24 Jun 2019 21:25:49 +0200

principle

Java classes are stored in strictly formatted. class files, which have sufficient metadata to parse all elements in the class: Class name, method, attribute, java bytecode instruction. ASM reads the above information from the class file and provides an interface to access and modify the information, thus changing the original behavior of the class. For ASM, java class is described as a tree, and ASM uses Visitor mode to traverse the entire binary structure.

Reflections, Proxy, metadata, and ASM libraries help Java achieve dynamic language capabilities.

Proxy must be interface-based, Cglib does not need it.

Usage

If the class modification is one-time and the original class information is known, the modified class file can be compiled directly through ASM and saved to the hard disk, and then run without relying on ASM, which is no different from the ordinary class. This way usually requires custom ClassLoader.

If you don't want to change the original function of the class, just modify/add some class information during the run time, such as dynamic proxy, AOP, etc., you can hang a user-defined hook program in the Java virtual machine at startup, and use ASM to change the bytecode of the specific class when loading the specific class, so as to change the class behavior.

Perform AOP

Target operation class:

public class TestBean {
    public void asmEcho(){
        System.out.println("hello asm");
    }
}

Enhanced Operations Class:

public class AopInteceptor {
    public static void before(){
        System.out.println(".......before().......");
    }

    public static void after(){
        System.out.println(".......after().......");
    }
}

AopClassAdapter inherits ClassVisitor:

public class AopClassAdapter extends ClassVisitor implements Opcodes {
    public AopClassAdapter(int i, ClassVisitor classVisitor) {
        super(i, classVisitor);
    }

    public void visit(
            int version,
            int access,
            String name,
            String signature,
            String superName,
            String[] interfaces) {
        //Change the class name and make the new class inherit the original class.
        super.visit(version, access, name + "_tmp", signature, name, interfaces);
        {//Output a default construction method
            MethodVisitor mv = super.visitMethod(ACC_PUBLIC, "<init>",
                    "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, name, "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
    }

    public MethodVisitor visitMethod(
            int access,
            String name,
            String desc,
            String signature,
            String[] exceptions) {
        if ("<init>".equals(name))
            return null;//Abandon all construction methods in the original class
        if (!name.startsWith("asm"))
            return null;// Execute proxies only for methods that start with asm

        MethodVisitor mv = super.visitMethod(access, name,
                desc, signature, exceptions);
        return new AopMethodVisitor(this.api, mv);
    }
}

AopMethodVisitor inherits MethodVisitor:

public class AopMethodVisitor extends MethodVisitor implements Opcodes {
    public AopMethodVisitor(int i, MethodVisitor methodVisitor) {
        super(i, methodVisitor);
    }

    public void visitCode(){
        super.visitCode();
        this.visitMethodInsn(INVOKESTATIC,"com/xxx/beecho/framework/asm/AopInteceptor","before","()V",false);
    }

    public void visitInsn(int opcode) {
        if (opcode >= IRETURN && opcode <= RETURN)//Before the method returns
        {
            this.visitMethodInsn(INVOKESTATIC, "com/xxx/beecho/framework/asm/AopInteceptor", "after", "()V", false);
        }
        super.visitInsn(opcode);
    }
}

For the bytecode generated by ASM, the proxy class is generated by Classloader:

public class AopClassLoader extends ClassLoader implements Opcodes {

    public AopClassLoader() {
        super();
    }

    public AopClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!name.endsWith("_tmp"))
            return super.loadClass(name);
        try {
            ClassWriter cw = new ClassWriter(0);
            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/xxx/beecho/framework/asm/TestBean.class");
            ClassReader reader = new ClassReader(is);
            reader.accept(new AopClassAdapter(ASM4, cw), ClassReader.SKIP_DEBUG);
            byte[] code = cw.toByteArray();

//            // -----
//            FileOutputStream fos = new FileOutputStream("E:\\code\\java\\TestBean_tmp.class");
//            fos.write(code);
//            fos.flush();
//            fos.close();

            return this.defineClass(name, code, 0, code.length);
        } catch (Throwable e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
//        return null;
    }
}

Function

    public static void main( String[] args ) throws IOException {
        try {
            AopClassLoader classLoader = new AopClassLoader();
            Class<?> asmClass = classLoader.loadClass("com.xxx.beecho.framework.asm.TestBean_tmp");

            Object obj = asmClass.newInstance();
            Method method = asmClass.getMethod("asmEcho",null);
            method.invoke(obj,null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
.......before().......
hello asm
.......after().......

Code address

> https://github.com/zhangcj/code-example/tree/master/framework/src/main/java/com/xxx/beecho/framework

Topics: Java Attribute github