AOP原理分析(二)执行流程

前言

根据上篇文章的介绍,当我们创建好AOP的代理对象后,当增强的方法被调用后,就会走到DynamicAdvisedInterceptor的intercept方法的逻辑,也正是在此Spring会组装好对调用方法的拦截器链,然后通过责任链模式执行,从而实现拦截目标方法的逻辑。

AOP执行的三大步

总览AOP执行流程

首先,一起看看代理拦截的执行逻辑吧

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Object target = null;
            TargetSource targetSource = this.advised.getTargetSource();
            try {//exposeProxy:暴露代理对象,使用了代理对象就有了增强功能
                if (this.advised.exposeProxy) {
                    // Make invocation available if necessary.
                    oldProxy = AopContext.setCurrentProxy(proxy);//使用ThreadLocal线程共享这个代理对象
                    setProxyContext = true;
                }
                target = targetSource.getTarget();
                Class<?> targetClass = (target != null ? target.getClass() : null);
                //责任链模式在执行目标方法前后执行其他的通知方法
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//通知方法转拦截器链
                Object retVal;
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                    // 直接反射执行目标方法
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = methodProxy.invoke(target, argsToUse);
                }
                else {
                    //创建一个方法执行的东西(拦截器链在此执行) 
                    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
                }
                retVal = processReturnType(proxy, target, method, retVal);
                return retVal;
            }
            finally {
                if (target != null && !targetSource.isStatic()) {
                    targetSource.releaseTarget(target);
                }
                if (setProxyContext) {
                    // Restore old proxy.
                    AopContext.setCurrentProxy(oldProxy);
                }
            }
        }

其实,总体看下来,这个方法主要干了三件事:

  1. 根据exposeProxy的值来决定是否暴露当前的代理对象
  2. 将当前执行方法的增强器转换为拦截器链
  3. 责任链模式执行拦截器链逻辑
    既然干了哪三件事情我们已经知道了,那我们接下来就挨个的来看Spring是如何干这三件事的:

第一步:是否暴露代理对象

在开启AOP的注解模式的@EnableAspectJAutoProxy注解中有一个exposeProxy属性,决定了是否暴露生成的代理对象,默认是false的,即不暴露。当我们设置exposeProxy=true后,就会触发AopContext.setCurrentProxy(proxy)的逻辑,我们看一下AopContext的代码:

    public final class AopContext {

    private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");

    private AopContext() {
    }

    public static Object currentProxy() throws IllegalStateException {
        Object proxy = currentProxy.get();
        if (proxy == null) {
            throw new IllegalStateException(
                    "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and " +
                            "ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.");
        }
        return proxy;
    }

    @Nullable
    static Object setCurrentProxy(@Nullable Object proxy) {
        Object old = currentProxy.get();
        if (proxy != null) {
            currentProxy.set(proxy);
        }
        else {
            currentProxy.remove();
        }
        return old;
    }

}

主要是使用了ThreadLocal来存储了生成的代理对象,这样我们就可以在当前线程中拿到这个线程对象进行其他操作了。有人会问:有什么实际的应用吗?还真有,其实这是我们后面会谈的Spring的事务也是基于AOP这一套的代理拦截模式的,我们知道Spring的事务如果方法内调用就会导致事务失效,这也很好理解,因为非事务方法内部调用事务方法时,只会直接反射调用执行非事务方法的逻辑,执行到事务方法时是不会再走代理拦截的逻辑了,那我们如何让他也走代理拦截的逻辑呢,那就需要通过代理类来调用事务方法才行,那这个代理类我们就可以在AopContext中获取,即拿到当前暴露的代理对象。

第二步:增强器转换为拦截器链

我们需要将当前执行方法的增强器Advice转换为拦截器(MethodInterceptor)链,因为最终的执行是通过责任链的模式来进行拦截执行的,所以首先需要将拦截器链准备好,我们来看下整个转换为拦截器的整个流程(如下图):


Advice转换流程.png

从上图中,我们可以看到整个的转换流程为:
先是拿到所有的Advisor,然后更具Advisor的类型分别来判断这个Advisor是否能拦截当前执行的方法,上文中介绍Advisor的时候,我们知道Spring中提供了两个子接口,分别是PointcutAdvisorIntroductionAdvisor,两者的区别我们也知道:PointcutAdvisor是方法级别的切入,所以需要先验证类过滤器然后再验证方法匹配器,都通过了再进行转拦截器;而IntroductionAdvisor是类级别的切入,所以我们只需要验证类过滤器通过就可以进行转拦截器了。
Spring提供了DefaultAdvisorAdapterRegistry这个类来帮助我们将Advice转换为拦截器,而所谓的拦截器就是Spring家定义的MethodInterceptor接口,在Spring中Advice并不是都是MethodInterceptor类型的,AOP中的AspectJMethodBeforeAdvice和AspectJAfterReturningAdvice就没有直接实现MethodInterceptor,所以在转换的时候,提供了两个适配器来适配这些不是MethodInterceptor类型的Advice。
我相信很多小伙伴都这里都会问:为什么有的Advice不直接实现MethodInterceptor呢?如果我们自己扩展一个Advice,那我肯定直接实现MethodInterceptor啊,这多方便,而Spring自己却有的需要进行适配器进行适配呢?这个问题得问Spring的开发人员了,两种方式都可以实现,获取只是Spring开发人员告诉你这里可以使用适配器模式吧哈哈哈,也说不定,后面版本就把它替换了呢,咱不纠结这个小问题。

构建责任链模式

有了拦截链集合,然后就是需要构建出责任链的调用了。
责任链调用的核心是在构建责任链处理器,这个处理器控制了整个责任链的流转调用,代码如下:

    public Object proceed() throws Throwable {
        //判断当前拦截器的索引有没有超过 拦截器总数量-1
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();//调用目标方法
        }
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//默认值为-1所以先++
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { 
            // 执行带参数的动态方法匹配
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
            // 执行带参数的动态方法匹配
            if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {.
                // 动态参数匹配失败,则跳过该拦截器
                return proceed();
            }
        }
        else { // MethodInterceptor类型的,不需要进行带参数的动态校验了
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); // 传入责任链处理器对象是关键
        }
    }

责任链处理器对象中,有着拦截器链的集合interceptorsAndDynamicMethodMatchers,以及一个拦截器链执行到哪的计数索引currentInterceptorIndex,其中需要注意的同样也是实现拦责任链模式的核心就是执行拦截器逻辑时,需要传入处理器对象,这样才能调回到处理器的逻辑中,继续进行下一个拦截器链的调用,从而一环一环的调用下去。我们可以发现,AOP的目标方法是在所有的拦截器都走过了的最后才触发调用的,我们总体看一下AOP的一个调用流程吧(如下图):


AOP责任链执行流程.png

写在最后

其实,聊到这里整个AOP的执行流程基本上就讲完了,但其实关于AOP的方法论才刚刚开始,从AOP看来Spring为我们提供了一种拦截代理对象然后进行过滤,构建责任链插入自定义逻辑的规范框架,我们可以在这个规范之上,进行二次开发,能够扩展出许多我们自己业务场景需要的,下节,我们继续聊聊Spring提供的这个模式是这么样的,以及Spring中还有哪些功能点套用了这一套模式。从功能点总结出思想,这样我们掌握了思想就是掌握了一类功能点的原理,对于我们二次开发的帮助是十分大的,我相信下一节的AOP模式总结对你帮助很大,敬请期待吧......

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

推荐阅读更多精彩内容