Spring注解驱动开发第32讲——拦截器链的执行过程
发布日期:2021-06-30 17:56:18 浏览次数:2 分类:技术文章

本文共 8042 字,大约阅读时间需要 26 分钟。

文章目录

写在前面

通过上一讲的研究,我们知道,在目标方法执行之前,Spring会把所有的增强器转换为拦截器,并最终形成一个拦截器链,然后根据这个拦截器链new出一个CglibMethodInvocation对象,接着会调用其proceed()方法。

接下来,我们就来探究一下这个proceed()方法到底怎么执行的,你只要搞清楚了,那么你必然就能知道整个拦截器链的执行过程。更具体一点的说,你会知道目标方法的整个执行流程,即目标方法是怎么一执行,然后先来执行前置通知,接着再是目标方法,紧接着再是后置通知,最后再是返回通知或者异常通知的。

拦截器链的执行过程

我们还是以debug模式来运行IOCTest_AOP测试类,这时,应该还是会来到AbstractAdvisorAutoProxyCreator抽象类的setBeanFactory()方法中,如下图所示。

在这里插入图片描述

程序是怎么一步一步到这儿的,我想之前我已经说得够多的了,这里我就不再赘述一遍了。那接下来该怎么办呢?我们按下F8快捷键让程序运行到下一个断点,一直运行到这个即将要执行的目标方法处,也就是说我们以前看过的方法就直接跳过了。

在这里插入图片描述

然后我们继续按下F8快捷键让程序运行到下一个断点,这时程序会运行到DefaultAdvisorAdapterRegistry类的getInterceptors()方法中,如下图所示。

在这里插入图片描述

你是不是现在倍感熟悉,因为此时就是将遍历出的每一个增强器转成拦截器的过程,我在上一讲中也详细讲过了,是真的讲得很详细哟😊

我们不妨点击方法调用栈的下面一行看看,如下图所示,inspect一下config.getAdvisors()表达式的值,便能看到那5个增强器了。

在这里插入图片描述

还是回到DefaultAdvisorAdapterRegistry类的getInterceptors()方法中,继续按下F8快捷键让程序运行到下一个断点,反复4次之后,可以看到现在传递过来的是第五个Advisor,该Advisor持有的增强器是AspectJMethodBeforeAdvice,即前置通知。

在这里插入图片描述

接下来,按下F6快捷键让程序往下运行,一直运行到CglibAopProxy类的intercept()方法中,如下图所示。

在这里插入图片描述

此时,将要执行的目标方法的拦截器链就获取到了。

如果真的获取到了拦截器链,那么接下来会怎么做呢?很明显,这时程序会进入到else分支语句中,然后将需要执行的目标对象、目标方法以及拦截器链等所有相关信息传入CglibMethodInvocation类的有参构造器中,来创建一个CglibMethodInvocation对象。

在这里插入图片描述

那到底是怎么来new这个CglibMethodInvocation对象的呢?我们可以这样做:先按下F5快捷键进入当前方法中,再按下F7快捷键从当前方法里面退出来,然后再按下F5快捷键进入当前方法中,这时程序会进入到CglibMethodInvocation匿名内部类的有参构造器中,如下图所示。

在这里插入图片描述

最后按下F7快捷键从当前方法里面退出来。至此,就new出来了这个CglibMethodInvocation对象。new出来该对象以后,接下来就是来执行其proceed()方法了。

以上就是上一讲中我们研究的内容。

接下来,我们就进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,这儿首先是有一个成员变量,即currentInterceptorIndex,翻译过来应该是当前拦截器的索引。

在这里插入图片描述

该索引的默认值是-1,点进该成员变量里面一看便知。

在这里插入图片描述

然后,会在这儿做一个判断,即判断currentInterceptorIndex成员变量的值(也即索引的值)是否等于this.interceptorsAndDynamicMethodMatchers.size() - 1。你可能要问了,这个interceptorsAndDynamicMethodMatchers到底是什么啊?inspect一下它便知道了,如下图所示。

在这里插入图片描述

可以看到,interceptorsAndDynamicMethodMatchers其实就是一个ArrayList集合,它里面保存有5个拦截器。

也就是说,在这儿是来判断-1是否等于5-1的,很显然,并不相等,小学生都知道。你又要问了,那什么时候相等呢?这得分两种情况来看:

  1. 如果我们没有获取到拦截器链,那么该ArrayList集合必然就是空的,此时就相当于是在判断-1是否等于0-1,你说等不等呢?
  2. currentInterceptorIndex成员变量是来记录我们当前拦截器的索引的(从-1开始),有可能正好当前拦截器的索引为4,此时就相当于是在判断4是否等于5(拦截器总数)-1,你说等不等呢?

不管是哪种情况,程序都会进入到if判断语句中。就以第一种情况来说,此时并没有拦截器链,那么必然就会调用invokeJoinpoint()方法。我们可以点进该方法里面去一探究竟,发现进入到了ReflectiveMethodInvocation类的invokeJoinpoint()方法中,如下图所示。

在这里插入图片描述

再点进invokeJoinpointUsingReflection()方法里面,发现其实就是利用反射来执行目标方法,如下图所示。

在这里插入图片描述

所以,我们可以得出这样一个结论:如果没有拦截器链,或者当前拦截器的索引和拦截器总数-1的大小一样,那么便直接执行目标方法。 我们先分析到这,因为过一会就可以看到这个过程了。

好,我们还是回到proceed()方法里面,此时是来判断currentInterceptorIndex成员变量的值(即-1)是否等于拦截器总数(5)-1的,很显然并不相等,所以程序并不会进入到if判断语句中。

按下F6快捷键让程序往下运行,运行至第162行代码处时,可以看到会先让currentInterceptorIndex成员变量自增,即由-1自增为0,然后再从拦截器链里面获取第0号拦截器,即ExposeInvocationInterceptor。

在这里插入图片描述

也就是说,在proceed()方法里面,我们会先来获取到第0号拦截器。第0号拦截器我们拿到以后,那么接下来该怎么办呢?继续按下F6快捷键让程序往下运行,发现运行到了第179行代码处,如下图所示。

在这里插入图片描述

可以看到,会调用当前拦截器的invoke()方法,而且会将this指代的对象传入该方法中。那么this指代的又是哪一个对象呢?inspect一下this,我们便知道它指代的就是之前创建的CglibMethodInvocation对象。

在这里插入图片描述

接下来,我们就进去invoke()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,这儿是先从invocation里面get到一个MethodInvocation实例。

在这里插入图片描述

你不禁就要问了,这个invocation是啥?这个MethodInvocation又是啥?我来一一解答你的疑问,点击invocation成员变量进去看一下,可以看到它是一个ThreadLocal,ThreadLocal就是同一个线程来共享数据的,它要共享的数据就是MethodInvocation实例,而这个MethodInvocation实例其实就是之前创建的CglibMethodInvocation对象。

在这里插入图片描述

由于这是第一次,ThreadLocal里面还没有共享数据,因此接下来便会在当前线程中保存CglibMethodInvocation对象。然后就会来执行CglibMethodInvocation对象的proceed()方法,如下图所示。

在这里插入图片描述

说白了,执行拦截器的invoke()方法其实就是执行CglibMethodInvocation对象的proceed()方法。

接下来,我们就再进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,你会发现这又是同样熟悉的那套,只不过现在是来判断currentInterceptorIndex成员变量的值(即0,因为自增过一次,所以已经由之前的-1变成了0)是否等于拦截器总数(5)-1的,很显然并不相等,所以程序并不会进入到if判断语句中。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,运行至第162行代码处时,可以看到会先让currentInterceptorIndex成员变量自增,即由0自增为1,然后再从拦截器链里面获取第1号拦截器,即AspectJAfterThrowingAdvice。

在这里插入图片描述

也就是说,每执行一次proceed()方法,当前拦截器的索引(即currentInterceptorIndex成员变量)都会自增一次。

第1号拦截器我们拿到以后,那么接下来该怎么办呢?继续按下F6快捷键让程序往下运行,发现运行到了第179行代码处,如下图所示。

在这里插入图片描述

可以看到,又会调用当前拦截器的invoke()方法,并且会将CglibMethodInvocation对象传入该方法中。

这时,你会发现,现在是从第一个拦截器(即ExposeInvocationInterceptor)锁定到了下一个拦截器(即AspectJAfterThrowingAdvice),而且我们也看到了,所有拦截器都会调用invoke()方法。

接着,我们就再进去invoke()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,当前拦截器(即AspectJAfterThrowingAdvice)的invoke()方法的执行逻辑是下面这样子的。

在这里插入图片描述

可以看到又是来调用CglibMethodInvocation对象的proceed()方法,其中mi变量指代的就是CglibMethodInvocation对象。

至此,我们可以得出这样一个结论:每执行一次proceed()方法,当前拦截器的索引(即currentInterceptorIndex成员变量)都会自增一次,并且还会拿到下一个拦截器。这个流程会不断地循环往复,直至拿到最后一个拦截器为止。

接下来,我们就再进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,你会发现又是同样熟悉的那套,只不过现在是来判断currentInterceptorIndex成员变量的值(即1,因为自增过一次,所以已经由之前的0变成了1)是否等于拦截器总数(5)-1的,很显然并不相等,所以程序并不会进入到if判断语句中。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,运行至第162行代码处时,可以看到会先让currentInterceptorIndex成员变量自增,即由1自增为2,然后再从拦截器链里面获取第2号拦截器,即AfterReturningAdviceInterceptor。

在这里插入图片描述

第2号拦截器我们拿到以后,那么接下来该怎么办呢?继续按下F6快捷键让程序往下运行,发现运行到了第179行代码处,如下图所示。

在这里插入图片描述

可以看到,又会调用当前拦截器的invoke()方法,并且会将CglibMethodInvocation对象传入该方法中。

这时,你会发现,现在是从上一个拦截器(即AspectJAfterThrowingAdvice)锁定到了当前这个拦截器(即AfterReturningAdviceInterceptor),可想而知,当前这个拦截器又该要锁定到下一个拦截器了。

接着,我们就再进去invoke()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,又是来调用CglibMethodInvocation对象的proceed()方法,其中mi变量指代的就是CglibMethodInvocation对象。

在这里插入图片描述

接下来,我们就再进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,你会发现又是同样熟悉的那套,只不过现在是来判断currentInterceptorIndex成员变量的值(即2,因为自增过一次,所以已经由之前的1变成了2)是否等于拦截器总数(5)-1的,很显然并不相等,所以程序并不会进入到if判断语句中。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,运行至第162行代码处时,可以看到会先让currentInterceptorIndex成员变量自增,即由2自增为3,然后再从拦截器链里面获取第3号拦截器,即AspectJAfterAdvice。

在这里插入图片描述

第3号拦截器我们拿到以后,那么接下来该怎么办呢?继续按下F6快捷键让程序往下运行,发现运行到了第179行代码处,如下图所示。

在这里插入图片描述

可以看到,又会调用当前拦截器的invoke()方法,并且会将CglibMethodInvocation对象传入该方法中。

这时,你会发现,现在是从上一个拦截器(即AfterReturningAdviceInterceptor)锁定到了当前这个拦截器(即AspectJAfterAdvice),可想而知,当前这个拦截器又该要锁定到下一个拦截器了。

接着,我们就再进去invoke()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,又是来调用CglibMethodInvocation对象的proceed()方法,其中mi变量指代的就是CglibMethodInvocation对象。

在这里插入图片描述

现在,你是否明白了这样一个道理,就是invoke()方法里面会调用proceed()方法,而这个proceed()方法又是寻找下一个拦截器

接下来,我们就再进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,你会发现又是同样熟悉的那套,只不过现在是来判断currentInterceptorIndex成员变量的值(即3,因为自增过一次,所以已经由之前的2变成了3)是否等于拦截器总数(5)-1的,很显然并不相等,所以程序并不会进入到if判断语句中。

在这里插入图片描述

继续按下F6快捷键让程序往下运行,运行至第162行代码处时,可以看到会先让currentInterceptorIndex成员变量自增,即由3自增为4,然后再从拦截器链里面获取第4号拦截器,即MethodBeforeAdviceInterceptor,它已是最后一个拦截器了。

在这里插入图片描述

最后一个拦截器我们拿到以后,那么接下来该怎么办呢?继续按下F6快捷键让程序往下运行,发现运行到了第179行代码处,如下图所示。

在这里插入图片描述

可以看到,又会调用当前拦截器的invoke()方法,并且会将CglibMethodInvocation对象传入该方法中。

这时,你会发现,现在是从上一个拦截器(即AspectJAfterAdvice)锁定到了当前最后这个拦截器(即MethodBeforeAdviceInterceptor)。

接着,我们就再进去invoke()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,可以看到,现在这个invoke()方法里面的逻辑有点变化,它会先调用前置通知,再来调用CglibMethodInvocation对象的proceed()方法,其中mi变量指代的就是CglibMethodInvocation对象。

在这里插入图片描述

调用前置通知

继续按下F6快捷键让程序往下运行,会发现MethodBeforeAdviceInterceptor这个拦截器总算是做一点事了,即调用前置通知,并且Eclipse控制台也打印出了前置通知要输出的内容,如下图所示。

在这里插入图片描述

前置通知调用完之后,会再来调用CglibMethodInvocation对象的proceed()方法。

接下来,我们就再进去proceed()方法里面去看一看,它到底是怎么执行的。按下F5快捷键进入该方法中,你会发现又是同样熟悉的那套,只不过现在是来判断currentInterceptorIndex成员变量的值(即4,因为自增过一次,所以已经由之前的3变成了4)是否等于拦截器总数(5)-1的,很显然是相等的,所以程序会进入到if判断语句中。

在这里插入图片描述

这时,会直接利用反射来执行目标方法。继续按下F6快捷键让程序往下运行,你会看到Eclipse控制台打印出了目标方法中要输出的内容,这表明目标方法已执行完了。

在这里插入图片描述

也就是说,前置通知调用完之后,接着是来调用目标方法的,并且目标方法调用完之后会返回到上一个拦截器(即AspectJAfterAdvice)中。

调用后置通知

执行完目标方法并返回到上一个拦截器(即AspectJAfterAdvice)中之后,可以看到会在finally代码块中执行后置通知,因为AspectJAfterAdvice是一个后置通知的拦截器。

继续按下F6快捷键让程序往下运行,你会看到Eclipse控制台打印出了后置通知要输出的内容,这表明当前拦截器(即AspectJAfterAdvice)的invoke()方法就调用完了。

在这里插入图片描述

从上图中可以知道,调用完后置通知之后,再次返回到了第二个拦截器(即AspectJAfterThrowingAdvice)中。

如果目标方法运行时没有抛异常,那么调用返回通知

如果目标方法运行时没有抛异常,那么后置通知调用完之后,就应该返回到第三个拦截器(即AfterReturningAdviceInterceptor)中。

而目前的这个目标方法运行时抛了异常,那么在调用完后置通知之后,其实按理来说应该是要返回到第三个拦截器(即AfterReturningAdviceInterceptor)中的,但是这个拦截器并没有对异常进行处理,而是直接抛给了上一个拦截器(即AspectJAfterThrowingAdvice),所以,你会看到,最终是返回到了第二个拦截器(即AspectJAfterThrowingAdvice)中。

在这里插入图片描述

在这个拦截器的invoke()方法中才有catch语句捕获到异常。

我们可以来看一下AfterReturningAdviceInterceptor这个拦截器的invoke()方法,看看它到底是怎么执行的,如下图所示。

在这里插入图片描述

可以看到,只有下面这行代码执行时没有问题,才会调用返回通知。

Object retVal = mi.proceed();

而且,咱们在以上invoke()方法中,并没有看到try catch代码块,所以,一旦以上代码运行时抛了异常,那么便会直接抛给上一个拦截器。

也就是说,返回通知只有在目标方法运行没抛异常的时候才会被调用。

如果目标方法运行时抛异常,那么调用异常通知

但是,现在目标方法运行时抛了异常,所以在后置通知调用完之后,返回到了第三个拦截器(即AfterReturningAdviceInterceptor)中。

在这里插入图片描述

该拦截器捕获到异常之后,便会调用异常通知。按下F6快捷键让程序往下运行,你会看到Eclipse控制台打印出了异常通知要输出的内容,如下图所示。

在这里插入图片描述

异常通知调用完之后,如果有异常,整个的这个异常还会被抛出去,而且是一层一层地往上抛,有人处理就处理,没人处理就抛给虚拟机。

至此,整个拦截器链的执行过程,我们就知道的非常清楚了。更具体一点的说,我们知道了目标方法的整个执行流程,即先执行前置通知,然后再来执行目标方法,接着再来执行后置通知,这三步是固定的。最后,如果目标方法运行时没有抛异常,那么调用返回通知,如果目标方法运行时抛了异常,那么调用异常通知。

在这里插入图片描述

小结

整个拦截器链的执行过程,我们总结一下,其实就是链式获取每一个拦截器,然后执行拦截器的invoke()方法,每一个拦截器等待下一个拦截器执行完成并返回以后,再来执行其invoke()方法。通过拦截器链这种机制,保证了通知方法与目标方法的执行顺序。

转载地址:https://liayun.blog.csdn.net/article/details/111603408 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第33讲——AOP原理总结
下一篇:Spring注解驱动开发第31讲——目标方法的拦截逻辑

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月17日 05时00分26秒