1. 程式人生 > >位元組碼實踐 -- 使用 ASM 實現 AOP

位元組碼實踐 -- 使用 ASM 實現 AOP

     ASM 是一個 Java 位元組碼操控框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進位制 class 檔案,也可以在類被載入入 Java 虛擬機器之前動態改變類行為。Java class 被儲存在嚴格格式定義的 .class 檔案裡,這些類檔案擁有足夠的元資料來解析類中的所有元素:類名稱、方法、屬性以及 Java 位元組碼(指令)。ASM 從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類。

     可以負責任的告訴大家,ASM只不過是通過 “Visitor” 模式將 “.class” 類檔案的內容從頭到尾掃描一遍。因此如果你抱著任何更苛刻的要求最後都將失望而歸。我們常見的 Aop 框架幾乎都屬於 ASM 框架的泛生品。

    眾所周知,Aop 無論概念有多麼深奧。它無非就是一個“Proxy模式”。被代理的方法在呼叫前後作為代理程式可以做一些預先和後續的操作。這一點想必讀者都能達到一個共識。因此要想實現 Aop 的關鍵是,如何將我們的程式碼安插到被呼叫方法的相應位置。

好了,開始正題,先寫個最簡單的"Hello xifeijian",程式碼如下:

package com.example.demo.controller.aop;

/**
 * Created by uc on 2018/10/18.
 */
public class TestBean {
    public void halloAop(){
        System.out.println("Hello xifeijian");
    }
}

接下來我想在halloAop中實現AOP,在呼叫該方法之前分別呼叫該類的before和after,怎麼實現?(具體功能不限,此處僅是demo)

package com.example.demo.controller.aop;

/**
 * Created by uc on 2018/10/18.
 */
public class AopInterceptor {
    public static void beforeInvoke() {
        System.out.println("before");
    };
    public static void afterInvoke() {
        System.out.println("after");
    };
}

下面開始安插 Aop 實現的 ASM 程式碼:

package com.example.demo.controller.aop;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

import java.io.InputStream;

/**
 * Created by uc on 2018/10/18.
 */
 public class AopClassLoader extends ClassLoader implements Opcodes {
    public AopClassLoader(ClassLoader parent) {
        super(parent);
    }
    public  Class<?> loadClass(String name) throws ClassNotFoundException {
        if (!name.contains("TestBean_Tmp"))
            return super.loadClass(name);
        try {
            ClassWriter cw = new ClassWriter(0);
            //
            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/example/demo/controller/aop/TestBean.class");
            ClassReader reader = new ClassReader(is);
            reader.accept(new AopClassAdapter(ASM4, cw), ClassReader.SKIP_DEBUG);
            //
            byte[] code = cw.toByteArray();
            //            FileOutputStream fos = new FileOutputStream("c:\\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();
        }
    }
}

 接下來我們實現一個ClassAdapter,程式碼如下:

package com.example.demo.controller.aop;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * Created by uc on 2018/10/18.
 */
public class AopClassAdapter extends ClassVisitor implements Opcodes {
    public AopClassAdapter(int api, ClassVisitor cv) {
        super(api, cv);
    }


    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        //更改類名,並使新類繼承原有的類。
        super.visit(version, access, name + "_Tmp", signature, name, interfaces);
        {
            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;
        if (!name.equals("halloAop"))
            return null;
        //
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        return new AopMethod(this.api, mv);
    }
}

最後就是使用 ASM 改寫 Java 類:

    package com.example.demo.controller.aop;
    
    import javassist.bytecode.Opcode;
    import jdk.internal.org.objectweb.asm.MethodVisitor;
    import jdk.internal.org.objectweb.asm.Opcodes;
    
    /**
     * Created by uc on 2018/10/18.
     */
    
    
    public class AopMethod extends MethodVisitor implements Opcodes{
        public AopMethod(int api, MethodVisitor mv){
            super(api ,mv);
        }
    
        public void visitCode(){
            super.visitCode();
            this.visitMethodInsn(INVOKESTATIC, "com/example/demo/controller/aop/AopInterceptor", "beforeInvoke", "()V");
        }
        public void visitInsn(int opcode){
            if(opcode==RETURN){
                mv.visitMethodInsn(INVOKESTATIC, "com/example/demo/controller/aop/AopInterceptor", "afterInvoke", "()V");
            }
            super.visitInsn(opcode);
        }
    
    }
    

 準備工作已經完成,最後我們只需要編寫一個 ClassLoader 載入我們的新類就可以了,新類的名稱後面多了“_Tmp”。

package com.example.demo.controller.aop;

import com.example.demo.DemoApplication;
import com.example.demo.aop.LogClassVisitor;
//import com.example.demo.aop.MyClassLoader;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import org.springframework.boot.SpringApplication;

/**
 * Created by uc on 2018/10/21.
 */
public class AsmAop {
    public static void main(String[] args)throws Exception
    {
        Class<?> clazz = new AopClassLoader(Thread.currentThread().getContextClassLoader()).loadClass("com.example.demo.controller.aop.TestBean_Tmp");
        clazz.getMethods()[0].invoke(clazz.newInstance());
    }
}

執行AsmAop類的main函式,得到以下結果,完成我們預期的AOP效果:

以上是該實踐的所有原始碼,輕輕鬆鬆Demo一下~