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

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

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

1. 什么是动态代理

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


image.png

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

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

JDK 动态代理

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


image.png

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, "<init>", "()V",
                null, null);
        mw.visitVarInsn(Opcodes.ALOAD, 0); // this 入栈
        mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
                "()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

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> 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> 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<User> thingyInstantiator = objenesis.getInstantiatorOf(User.class);
        User user = thingyInstantiator.newInstance();
        user.setName("hello");
        System.out.println(user.getName());
    }
}

Bean 后置处理器

image.png

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

image.png

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


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> T createProxy(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new DBQueryInterceptor(obj));
        return (T) enhancer.create();
    }
}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容

  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,709评论 2 22
  • 1.1 Spring IoC容器和bean简介 本章介绍了Spring Framework实现的控制反转(IoC)...
    起名真是难阅读 2,580评论 0 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,289评论 6 86
  • 1.请于签收24小时内联系客服售后; 2.因客户延迟签收、拒收不在理赔范围内; 3.破损果、坏果十个以内(包含十个...
    许铭阅读 789评论 0 0