字节码实践 -- 使用 ASM 实现 AOP

Posted 席飞剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节码实践 -- 使用 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一下~

以上是关于字节码实践 -- 使用 ASM 实现 AOP的主要内容,如果未能解决你的问题,请参考以下文章

字节码插桩AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

看这一篇,你也可以自如的掌握字节码插桩

看这一篇,你也可以自如的掌握字节码插桩

Android——面向AOP编程

Android——面向AOP编程

Android——面向AOP编程