一步一步教你写 SPRING AOP 准备工作

源码地址
https://github.com/yixuaz/myspring

在写AOP之前,我们必须要先会写,JAVA的动态代理,要知道代理模式。所以我们先把准备工作给做好。
我们会按照这个顺序来讲
1.什么是动态代理
2.JDK动态代理
3.CGLIB 动态代理
4.Objenesis的使用
5.bean后置处理器的使用

1. 什么是动态代理

在讲动态代理前,我们先说下什么是代理模式。
比如你MAIN方法想要去调用DBQUERY的request方法。但是你给MAIN的是一个接口。如下图。


一步一步教你写 SPRING AOP 准备工作_第1张图片
image.png

那么MAIN方法是不知道实现这个接口的是代理类还是类本身。
所以代理类可以封装了原类之后,对REQUEST方法做一个加强。比如在前面做一些安全效验。在后面做一个日志记录等。

那么动态代理的意思,就是这个代理类我们是不用自己去写这样一个类。而是由程序自动为我们生成,运行时生成,这就是动态代理了。
好处就是有很多类,比如FILE QUERY,都要有代理。就不用每个类都写一个代理类。

JDK 动态代理

JDK动态代理为我们提供的了一个INVOCATION HANDLER的接口。


一步一步教你写 SPRING AOP 准备工作_第2张图片
image.png

一步一步教你写 SPRING AOP 准备工作_第3张图片
image.png

下面我们就手把手来实现下JDK动态代理。
首先我们需要一个IDBQuery 的Interface

public interface IDBQuery {
    String request(String id);
}

写一个DBQuery 来实现它

public class DBQuery implements IDBQuery{

    @Override
    public String request(String id) {
        return "request id";
    }
}

实现一个代理类 实现JDK的INVOCATION HANDLER的接口

public class DBQueryProxy implements InvocationHandler {
    private Object tar;//要代理的对象
    public Object bind(Object obj){
        this.tar = obj;
        //取得代理对象
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),tar.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("query Id:"+args[0]);//增强代码
        Object result = method.invoke(tar,args);
        return result;
    }
}

最后写个MAIN函数做测试

public class JdkProxyMain {
    public static void main(String[] args) {
        DBQueryProxy proxy = new DBQueryProxy();
        IDBQuery query = (IDBQuery) proxy.bind(new DBQuery());
        System.out.println(query.request("1"));
        System.out.println("class:"+query.getClass());
   }
}

我们发现CLASS变了,已经编程代理类了。

CGLIB 动态代理

cglib 底层是通过字节码操作,来实现生成一个代理类,比JDK提供的方式更加灵活。
弥补了JDK动态代理智能代理接口的不足。

操作字节码是通过一个ASM的库。
我们先来看一下一个ASM库可以实现什么效果

下面这段代码就是通过ASM在运行时来动态生成一个Week11的class,这个类里有个MAIN方法。算(6+7)*3 然后打印出来。

public static void main(String[] args) throws Exception {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
                | ClassWriter.COMPUTE_FRAMES);
        cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "Week11", null,
                "java/lang/Object", null);
        // 方法开始init
        MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V",
                null, null);
        mw.visitVarInsn(Opcodes.ALOAD, 0); // this 入栈
        mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "",
                "()V");
        mw.visitInsn(Opcodes.RETURN);
        mw.visitMaxs(0, 0);
        mw.visitEnd(); // 方法init结束
        // main方法开始
        mw = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
                "([Ljava/lang/String;)V", null, null);
        /**
         * 使用ASM,通过字节码 完成以下代码: int a=6; int b=7; int c=(a+b)*3;
         * System.out.println(c);
         */
        // 把变量放入局部变量表里
        mw.visitIntInsn(Opcodes.BIPUSH, 6);
        mw.visitIntInsn(Opcodes.BIPUSH, 7);
        // 操作数栈
        mw.visitInsn(Opcodes.IADD);
        mw.visitIntInsn(Opcodes.BIPUSH, 3);
        mw.visitInsn(Opcodes.IMUL);
        mw.visitVarInsn(Opcodes.ISTORE, 2);
        // 打印出来
        mw.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                "Ljava/io/PrintStream;");
        mw.visitVarInsn(Opcodes.ILOAD, 2);
        mw.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
                "println", "(I)V");
        mw.visitInsn(Opcodes.RETURN);
        mw.visitMaxs(0, 0);

        mw.visitEnd(); // main方法结束

        final byte[] code = cw.toByteArray();

        ASMPlay loader = new ASMPlay();

        Class exampleClass = loader.defineClass("Week11", code, 0,
                code.length);
        exampleClass.getMethods()[0].invoke(null, new Object[] { null });

    }

cglib主要通过methodInterceptor

一步一步教你写 SPRING AOP 准备工作_第4张图片
image.png

JDK缺陷,要使用代理必须要有接口。CGLIB没有这个限制。
代码详解
DBQuery 类不用接口了

public class DBQuery {
    public String request(String id) {
        return "request id";
    }
    public String request2(String id) {
        return "request id2";
    }
}

代理器
System.out.println("query id:"+args[0]); 为增强方法。
后面就是执行代理方法。

public class DBQueryInterceptor implements MethodInterceptor {
    Object tar;
    public DBQueryInterceptor(Object tar){
        this.tar = tar;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("query id:"+args[0]);
        Object result = methodProxy.invoke(tar,args);
        return result;
    }
}

MAIN

public class CglibProxyMain {
    public static void main(String[] args) {
        DBQuery query = createProxy(new DBQuery());
        System.out.println(query.request("1"));
        System.out.println(query.request2("2"));
        System.out.println(query.getClass());
    }

    private static  T createProxy(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new DBQueryInterceptor(obj));
        return (T) enhancer.create();
    }
}

打印结果,我们发现这个类下面所有的方法都被增强了。

query id:1
request id
query id:2
request id2
class cglib.DBQuery$$EnhancerByCGLIB$$4a3cce79

那有没有方法可以选择增强部分方法呢?
这要在INTECEPT里面判断哪些方法用METHOD,哪些方法用METHOD PROXY 就好了

public class DBQueryInterceptor implements MethodInterceptor {
    Object tar;
    public DBQueryInterceptor(Object tar){
        this.tar = tar;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if(!method.getName().equals("request2")){
            return method.invoke(tar,args);
        }
        System.out.println("query id:"+args[0]);
        Object result = methodProxy.invoke(tar,args);
        return result;
    }
}

可以在ENHANCER的地方设置CALLBACK FILTER,来指定使用不同的代理方法。

filter 里面就是告诉CGLIB,我应该用CALLBACKS[] 里的第几个CALLBACK。

public class CglibProxyMain {
    public static void main(String[] args) {
        DBQuery query = createProxy(new DBQuery());
        System.out.println(query.request("1"));
        System.out.println(query.request2("2"));
        System.out.println(query.getClass());
    }

    private static  T createProxy(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallbacks(new Callback[]{
                new DBQueryInterceptor(obj), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("second");
                return methodProxy.invokeSuper(o,args);
            }
        }
        });
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                if(method.getName().equals("request2"))
                    return 1;
                return 0;
            }
        });

        return (T) enhancer.create();
    }

Objenesis

Objenesis是专门用于实例化一些特殊java对象的一个工具,如私有构造方法,带参数的构造等不能通过class.newInstance()实例化的,通过它可以轻松完成。

首先写一个没有默认构造函数的类。

public class User {
    String name;
    private User(){

    }
    private User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后用OBJENESIS把它构造出来

import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.ObjenesisStd;
import org.springframework.objenesis.instantiator.ObjectInstantiator;

public class ObjenesisMain {
    public static void main(String[] args) {
        Objenesis objenesis = new ObjenesisStd();
        ObjectInstantiator thingyInstantiator = objenesis.getInstantiatorOf(User.class);
        User user = thingyInstantiator.newInstance();
        user.setName("hello");
        System.out.println(user.getName());
    }
}

Bean 后置处理器

一步一步教你写 SPRING AOP 准备工作_第5张图片
image.png

通过SPRING源码 可以看到这个调用顺序

一步一步教你写 SPRING AOP 准备工作_第6张图片
image.png

我们在来看下Bean 后置处理器 在哪里注册上去的。 下图是一个SPRING 的 APLLICATION CONTEXT的一个流程图。
我们可以看到标红的那一步就是。
下面标红的那一步就是上面贴的代码来源。


一步一步教你写 SPRING AOP 准备工作_第7张图片
image.png

最后用BEAN POST PROCESSOR来模拟一个简单的AOP实现

public class AopBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean.getClass().equals(DBQuery.class)) {
            return createProxy(bean);
        }
        return bean;
    }

    private  T createProxy(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new DBQueryInterceptor(obj));
        return (T) enhancer.create();
    }
}

接下来我们就按照这个思路来实现自己的SPRING AOP。

你可能感兴趣的:(一步一步教你写 SPRING AOP 准备工作)