新年第一篇-另类AOP

724 阅读3分钟

Spring中最为重要的一个特性之一为AOP。它使得我们开发过程中一些棘手的情况下做了很好的处理(在不改动原本代码逻辑的前提下,前后增加额外的逻辑处理),让我们尽可能的少修改原本的代码块,使得我们的程序出错的可能性大降低。这里我就介绍一个不用框架而完成的一个AOP

ASM代码实现

需要增强类

public class Montos {
    public void say() {
        System.out.println("montos is a handsome boy");
    }
}

字节码的观察类

public class MyClassVisitor extends ClassVisitor implements Opcodes {

    public MyClassVisitor(ClassVisitor cv) {
        super(ASM5, cv);
    }

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

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature,String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
        //不增强构造方法
        if (!name.equals("<init>") && mv != null) {
            mv = new MyMethodVisitor(mv);
        }
        return mv;
    }
    /**
     * 对类内方法的观察
     */
    class MyMethodVisitor extends MethodVisitor implements Opcodes {
        public MyMethodVisitor(MethodVisitor mv) {
            super(Opcodes.ASM5, mv);
        }
        /**
         * 开始访问某一个方法的 Code 区时被调用 --- 类似于AOP的前置调用
         */
        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("show time begin");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
         /**
         * 判断了当前指令是否为无参数的“return”指令,如果是就在它的前面添加一些指令,也就是将 AOP的后置逻辑
         */
        @Override
        public void visitInsn(int opcode) {
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                // 方法在返回之前,打印 "show time end"
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("show time end");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            mv.visitInsn(opcode);
        }
    }
}

字节码生成类

public class Generator {

    public static void main(String[] args) throws Exception {
        // 读取字节码
        ClassReader classReader = new ClassReader("com/montos/asm/Montos");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        // 处理字节码
        ClassVisitor classVisitor = new MyClassVisitor(classWriter);
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);

        byte[] data = classWriter.toByteArray();
        // 输出
        File f = new File("绝对路径/target/classes/com/montos/asm/Montos.class");
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
        System.out.println("now generator cc success!!!!!");
    }
}

测试类

public class TestMain {
    public static void main(String[] args) {
        Montos montos = new Montos();
        montos.say();
    }
}

结果我们能发现,在第一次运行测试类的时候是按照源码里面编写的那样进行输出。当我们对基类进行加强,然后再运行测试类的时候,发现我们前后增加的语句输出了。

Javassist代码实现

需要增强类

public class Montos {
    public void say() {
        System.out.println("montos is a handsome boy");
    }
}

增强实现类

public class JavassistTest {
    public static void main(String[] args) throws NotFoundException,
            CannotCompileException, IllegalAccessException, InstantiationException,
            IOException {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("com.montos.javassist.Montos");
        CtMethod m = cc.getDeclaredMethod("say");
        m.insertBefore("{ System.out.println(\"show time begin\"); }");
        m.insertAfter("{ System.out.println(\"show time end\"); }");
        Class c = cc.toClass();
        cc.writeFile("绝对路径/target/classes/com/montos/javassist");
        Montos h = (Montos) c.newInstance();
        h.say();
    }
}

通过上面的Demo,我们也能发现,我们也是完成了对于一个类的增强实现。

总结

上面是通过两种简单的方式阐述了我们日常代码中的AOP实现方式。第一种是通过ASM,第二种是通过Javassist实现。ASM是在指令层次上操作字节码的,Javassist则强调通过源代码层次操作字节码 。两者都是从底层字节码作为最终解决方法的。只不过Javassist则更偏向于我们使用编程语言的人接受。

祝小伙伴新的一年顺乐顺乐顺乐~