原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9619910.html

  其实在之前的源码解读里面,关于织入的部分并没有说清楚,那些前置、后置、环绕、异常等通知是如何围绕在目标方法周围执行的呢?

  这里面最重要的就是递归,Spring在实现这块逻辑的时候使用的大量的递归调用,完美的实现的织入的逻辑。

  我们不凡就以Spring基础系列--AOP实践中的例子来进行一番逻辑追踪,来一探究竟。

  我们就从测试类开始:

  首先我们通过CglibAopProxy类的intercept方法构建一个方法调用,并执行其proceed方法:

  然后进入ReflectiveMethodInvocation类的proceed()方法,由于currentInterceptorIndex下标从-1开始,在进行++自增操作之后变成0,获取interceptorsAndDynamicMethodMatchers中的第一个拦截器。

  我们看下interceptorsAndDynamicMethodMatchers中的内容:

  第一个是ExposeInvocationInterceptor,这个正是之前在Spring基础系列-AOP源码分析中源码11里提到的,获取通知链的时候会将其放到通知链首位,用于暴露方法调用到ThreadLocal中。

  由于其为一个简单的拦截器,上面的判断是不成立的,所以直接执行最后一行:

  然后进入到ExposeInvocationInterceptor方法的invoke方法中

  在这个方法中我们可以看到,90行处,将方法调用mi放到了invocation中。那invocation是什么呢?如下

     private static final ThreadLocal<MethodInvocation> invocation = new NamedThreadLocal<>("Current AOP method invocation");

  可见,是将方法调用保存到了ThreadLocal中,这个invocation保存的正是当前AOP的方法调用。

  然后执行mi.proceed()方法,这就是一个递归调用了。在这里执行proceed方法,将会继续执行下一个通知,执行逻辑又回到ReflectiveMethodInvocation类的proceed()方法,这时currentInterceptorIndex自增之后为1,正好指向interceptorsAndDynamicMethodMatchers中的下一个通知:

  很显然,下一个通知为后置异常通知afterThrowingTest。

  执行这个通知的invoke方法,代码调到AspectJAfterThrowingAdvice类的invoke方法。

  在异常通知的invoke方法中直接递归调用proceed方法,并将剩下所有的调用执行全部try...catch住,在最后catch块中执行其通知逻辑。

  除开普通的拦截器之外,最先执行的通知是异常通知,它会将剩下所有的调用逻辑全部catch住,也就是说在这期间发生的任何异常都会被捕捉到,而且一旦哪一步发生了异常,那么执行就会被中断,到这里执行catch逻辑。

  这下又回到了ReflectiveMethodInvocation类的proceed方法。currentInterceptorIndex再次自增为2,指向通知链中第三个通知:

  后置返回通知,遵从以上的逻辑我们看看下一步的行为:

  在AfterReturningAdviceInterceptor类中

  果然去执行后置返回通知的invoke方法去啦,第一步任然是递归调用,又回到了ReflectiveMethodInvocation类的proceed方法去执行第四个通知:

 这个是后置终点通知,这个通知有点特殊哦,我们来看看其invoke方法

  它也是先直接进行递归调用,将其逻辑放在最后,这个是真正的最后,直接放到了finally块中,这表示什么,这表示,无论接下来的调用操作是否会发生异常,这部分逻辑永远会执行。

  现在又一次回去了ReflectiveMethodInvocation类的proceed方法去执行第五个通知:

  嗯,这次是环绕通知,这个通知不同于其他通知,其他通知只执行于方法的一点,这个通知却需要执行于方法的两处。所以它会拥有一个ProceedJoinPoint参数,这个参数就是用来区分这两处执行逻辑的:目标方法之前与之后。

  直接执行到最后一句,首次要执行invokeAdviceMethod方法了:

  然后执行invokeAdviceMethodWithGivenArgs方法:

  它要直接执行通知方法中的逻辑,想想为什么没有在进行递归调用而是直接执行的通知方法呢?

  因为环绕通知将递归调用挪移到了通知逻辑中,由程序员自定义执行,来看看我自定义的环绕通知逻辑:

  我们先不考虑具体的逻辑,先看看大体结构,显示一段逻辑,然后执行jp.proceed(os)方法。然后又是一段逻辑。所谓的环绕就是将逻辑围绕在目标执行前后。

  通知的内容真正开始执行了,这里首先执行了环绕通知的前置部分。

  我们执行递归调用之后,我们发现逻辑跑到了MethodInvocationProceedingJoinPoint中,这是啥类呢?

  这竟然是ProceedJoinPoint的实现类,我们执行jp.proceed(os)方法当然会跑到这里了,

  其实ProceedJoinPoint的proceed方法有两个重载,一个有参数,一个无参数,分别用于针对目标方法有参数和目标发无参数的情况,所以这里这两个方法其实逻辑类似,只是有参数的方法会针对参数进行一番操作,将参数设置到方法调用中,这么做,新的方法参数会替换就的方法参数,这也是我们可以在环绕通知中修改参数的原理所在。

  第99行就是重设参数的逻辑,方法调用中原先其实已经保存有参数:原有的参数。

  第100行代码是重点,这里这里创建了一个方法调用的一个浅拷贝,并使用这个浅拷贝来执行递归调用proceed方法。

  为什么使用浅拷贝呢?因为我们还希望使用与原来的方法调用相同的拦截器链和其他对象引用,只不过是需要当前的环绕通知的独立性罢了(这个可能就是再之前提到的环绕通知会导致其他一些通知功能失效的原因所在了吧)

逻辑又回到ReflectiveMethodInvocation类的proceed方法去执行第六个通知:

  这最后一个通知必然就是剩余的前置通知了。让我们看看其invoke方法执行。来自:MethodBeforeAdviceInterceptor

  这里执行通知的前置通知逻辑:来自:AspectJMethodBeforeAdvice

  调用跑到AbstractAspectJAdvice中:

  再调用:

  这里就是真正调用通知逻辑了:来自AspectTest

  然后就是一路后退到MethodBeforeAdviceInterceptor类中的invoke方法,继续执行下一步:

  这又是一递归调用,这个调用将会使逻辑再次返回到ReflectiveMethodInvocation类的proceed方法,这一次,由于 所有的通知链中的通知都走过一次,剩余的就是目标方法了。来自:CglibAopProxy

  所以这次直接执行目标方法了,哈哈。来自:AspectDemo

  反射调用目标方法。

  执行完目标方法,然后递归退回到AspectTest类中的环绕通知aroundTest中去执行环绕通知的后置部分逻辑。

  执行完后,继续递归退回,到AspectJAfterAdvice类的invoke方法,去执行finally中的逻辑:

  再次执行AbstractAspectJAdvice类的invokeAdviceMethod方法:

  调用invokeAdviceMethodWithGivenArgs方法

  这次执行后置终点通知中的内容:

  然后再次递归回退到AfterReturningAdviceInterceptor类的invoke方法:

  调用AspectJAfterReturningAdvice类的afterReturning方法:

  这里不再罗列AbstractAspectJAdvice类中的那两个方法啦,直接出通知方法:

  这次退回到AspectJAfterThrowingAdvice类的invoke方法:

  由于整套逻辑未发生异常,所以此处不执行catch块中的逻辑。然后再次递归回退到了ExposeInvocationInterceptor类的invoke方法,来执行finally块中的逻辑:

  这句话将invocation中保存的方法调用置空了。最后回退到DynamicAdvisedInterceptor类的intercept方法继续下面的逻辑。

  好了,到此为止,想说的都说啦,下面就是一些补充:

  这里是为了统一说明,所以讲所有的通知全部罗列,在一起调用,很明显返回值的部分就不正确,我想说的是,尽量别把环绕通知和别的通知一起使用,这个通知还是单独使用比较好。

  怎么样?如果你全都看完了,那么有啥感想吗?

  织入的实现就是依靠一个顺序通知链,再加上递归实现的。有没有感觉这里的递归用的非常的漂亮。层层嵌套,将后执行的部分放到通知链前面,在递归链中以先执行下一条通知的方式层层深入,最里面是首先执行的通知(前置通知,先把环绕通知撇开),然后再层层退出,执行各自通知的逻辑包括目标方法的逻辑。

  之前我有个疑惑就是在ReflectiveMethodInvocation类的proceed方法中,前三行代码,顺序执行通知链,最后执行目标方法,那么怎么实现后置的通知呢。看完上面的代码追踪,就了解了吧!

Spring基础系列--AOP织入逻辑跟踪的更多相关文章

  1. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  2. Spring基础系列--AOP实践

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9615720.html 本文目的是简单讲解下Spring AOP的使用. 推荐使用IDEA ...

  3. Spring基础系列-Spring事务不生效的问题与循环依赖问题

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一.提出问题 不知道你是否遇到过这样的情况,在ssm框架中开发we ...

  4. Spring AOP: 织入的顺序

    spring AOP 采用和 AspectJ 一样的优先顺序来织入增强处理:在进入连接点时,高优先级的增强处理将先被织入:在退出连接点时,高优先级的增强处理会后被织入. 当不同的切面里的两个增强处理需 ...

  5. Spring的LoadTimeWeaver(代码织入)

    在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入.编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中:而类加载期织入则指通过特 ...

  6. Spring的LoadTimeWeaver(代码织入)(转)

    https://www.cnblogs.com/wade-luffy/p/6073702.html 在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入. ...

  7. Spring基础只是—AOP的概念介绍

    Spring容器包含两个重要的特性:面向切面编程(AOP)和控制反转(IOC).面向切面编程是面向对象(OOP)的一种补充,在面向对象编程的过程中编程针对的目标是一个个对象,而面向切面编程中编程针对的 ...

  8. Spring基础系列-Web开发

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996902.html SpringBoot基础系列-web开发 概述 web开发就是集成 ...

  9. Spring基础系列-容器启动流程(1)

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9870339.html 概述 ​ 我说的容器启动流程涉及两种情况,SSM开发模式和Spri ...

随机推荐

  1. Linux下Shell重定向

    1. 标准输入,标准输出与标准错误输出 Linux下系统打开3个文件,标准输入,标准输出,标准错误输出. 标准输入:从键盘输入数据,即从键盘读入数据. 标准输出:把数据输出到终端上. 标准错误输出:把 ...

  2. Caused by: org.apache.ibatis.builder.BuilderException: Parsing error was found in mapping #{}. Check syntax #{property|(expression), var1=value1, var2=value2, ...}

    解决办法:查看与该项目中的所有#{},应该是 #{}的中间没有写值

  3. 自主学习python文本进度条及π的计算

    经过自己一段时间的学习,已经略有收获了!在整个过程的进行中,在我逐渐通过看书,看案例,做题积累了一些编程python的经验以后,我发现我渐渐爱上了python,爱上了编程! 接下来,当然是又一些有趣的 ...

  4. windows10下Kafka环境搭建

    内容小白,包含JDK+Zookeeper+Kafka三部分.JDK:1)   安装包:Java SE Development Kit 9.0.1      下载地址:http://www.oracle ...

  5. Cmd命令 查看端口被占用

    1)第一步 打开cmd命令窗口,输入命令:netstat -ano|findstr 输入端口号 2)第二步 继续输入命令:tasklist|findstr  第一步查询到的进程号 3)第三步 根据第二 ...

  6. Super Jumping! Jumping! Jumping! ---HDU - 1087

    Nowadays, a kind of chess game called “Super Jumping! Jumping! Jumping!” is very popular in HDU. May ...

  7. cadence布线完成后的补充操作

    完成布线之后,需要生成光绘文件和钻孔文件,在生成钻孔文件之前,还有几点补充!

  8. 超有料丨小白如何成功逆袭为年薪30万的Web安全工程师

    今天的文章是一篇超实用的学习指南,尤其是对于即将毕业的学生,新入职场的菜鸟,对Web安全感兴趣的小白,真的非常nice,希望大家能够好好阅读,真的可以让你少走很多弯路,至少年薪30万so easy! ...

  9. Java语言

    Java语言基础教程 本文将放入菜单栏中方便学习,记得点赞哦! Java分为3个体系,为JavaSE,JavaEE,JavaME,是一种面向对象的程序设计语言,记住Oracle公司收购了 Sum公司, ...

  10. SEO需要掌握的基础知识

    什么是SEO?  官方解释:  SEO是指通过对网站内部调整优化及站外优化,使网站满足搜索引擎收录排名需求,在搜索引擎中提高关键词排名, 从而把精准用户带到网站,获得免费流量,产生直接销售或品牌推广 ...