博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring5源码精讲(十一):AOP源码篇#3:目标方法调用与切面执行逻辑
阅读量:2066 次
发布时间:2019-04-29

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

目录

1、前言

在上一篇文章中,我们对AOP切面解析和代理对象创建有了一个大概的了解,切面和代理对象是没问题了,但是目标方法的调用,以及切面是在什么时候被执行的,我们还需要继续通过源码寻找答案。

2、目标方法调用

  1. 调用JdkDynamicAopProxy#invoke(…)方法

  2. 首先对代理类执行的方法进行判断,如果是没有重写equals或者hashcode等方法则不进行代理

    if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
    // The target does not implement the equals(Object) method itself. return equals(args[0]);}else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
    return hashCode();}else if{
    ……}
  3. 判断是否要暴露代理对象,即在@EnableAspectJAutoProxy(exposeProxy = true)标注了true时,会将当前代理对象存入ThreadLocal中,

    当需要使用该对象时,可以通过AopContext.currentProxy()调用

    // 判断是否要暴露代理对象if (this.advised.exposeProxy) {
    // Make invocation available if necessary. //放入线程本地变量 oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true;}

3、切面执行逻辑

  1. 从缓存中获取和当前方法相关的切面拦截链,由于是第一次调用,所以需要将之前解析出来的切面处理成Interceptor类型的对象再返回

    List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  2. 判断切面拦截链是否为空,如果为空的话直接使用反射调用方法返回结果即可

    if (chain.isEmpty()) {
    // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); }
  3. 由于代码存在before、after、afterReturn、afterThrowing切面,因此进入invocation.proceed()方法,ReflectiveMethodInvocation#proceed该方法也是拦截链的核心逻辑

    else {
    // We need to create a method invocation... MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed();}
  4. 判断当前下标是否为拦截链size-1,拦截器的size为5(一个spring自带的和四个自己定义的通知方法),当前下标this.currentInterceptorIndex的默认值为-1,因此如果不存在拦截器的话,size-1的逻辑还是可以正常退出的

    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
    return invokeJoinpoint();}
  5. 注意,这里是前++操作,因此拿到的值不会是-1,而是第0个。而这拦截链中第0个对象,是

    ExposeInvocationInterceptor。Object interceptorOrInterceptionAdvice =      this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  6. ExposeInvocationInterceptor的类型并不是InterceptorAndDynamicMethodMatcher,因此执行else逻辑,即调用ExposeInvocationInterceptord的invoke方法:

    else {
    // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);}
  7. ExposeInvocationInterceptor是一个静态单例对象,它在内部提供了一个存放MethodInvocation类型的ThreadLocal变量,用于存储上下文调用。并且它在调用链中总是第一个调用。在mi.proceed();方法中,实际上是ReflectiveMethodInvocation#proceed方法的一次递归

    public Object invoke(MethodInvocation mi) throws Throwable {
    MethodInvocation oldInvocation = invocation.get(); invocation.set(mi); try {
    return mi.proceed(); } finally {
    invocation.set(oldInvocation); }}
  8. 重复7、8步骤,此时拿到的下标为第1个,因为在该实例中,拦截器一共有五个,其中顺序依次为:ExposeInvocationInterceptor、AfterThrowing、AfterReturn、After、Before,因此此时得到的拦截器为异常通知AfterThrowing,同样执行else逻辑,调用AspectJAfterThrowingAdvice#invoke方法(此处又是一系列的模板方法设计模式的应用)

    public Object invoke(MethodInvocation mi) throws Throwable {
    try {
    return mi.proceed(); } catch (Throwable ex) {
    if (shouldInvokeOnThrowing(ex)) {
    invokeAdviceMethod(getJoinPointMatch(), null, ex); } throw ex; }}
  9. 从AspectJAfterThrowingAdvice#invoke方法可以看出,无论后续逻辑出现什么问题,都会被此处的catch语句捕捉,然后判断是否调用异常通知,最后抛出异常。而mi.proceed()方法则是又一次递归

  10. 重复7、8步骤,此时拿到的下标为第2个,即返回通知AfterReturn的invoke方法,该方法中通知方法是后于proceed()方法执行的,因此当出现异常时,返回通知是不会被执行到的:

    public Object invoke(MethodInvocation mi) throws Throwable {
    Object retVal = mi.proceed(); this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); return retVal;}
  11. 继续调用proceed方法,重复7、8步骤,此时拿到的下标为第3个,即后置通知After的invoke方法,从代码中可以看到,该方法是通过try{}finally{}的形式,保证了后置通知无论如何都会被执行到

    public Object invoke(MethodInvocation mi) throws Throwable {
    try {
    return mi.proceed(); } finally {
    invokeAdviceMethod(getJoinPointMatch(), null, null); }}
  12. 继续调用proceed方法,重复7、8步骤,此时拿到的下标为第4个,即前置通知Before的invoke方法,与afterReturn方法不同的是,它先于目标方法执行,因此前置通知不会受异常影响,并且先于其它通知执行

    @Overridepublic Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed();}
  13. 继续调用proceed方法,需要注意的是,此时this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1条件已经成立,直接执行目标方法。执行完毕之后再根据刚才的调用链执行,完成整个AOP的过程。

4、结尾

Spring在对增强器的处理过程采用了责任链的形式,很好的保证了扩展性,而整个AOP的功能又得益于之前IOC容器设计时预留的接口,我们在学习代码时,不止要知道Spring是怎么做的,还应该多思考Spring为什么是这么做的。

转载地址:http://smfmf.baihongyu.com/

你可能感兴趣的文章
Go语言学习Part2:流程控制语句:for、if、else、switch 和 defer
查看>>
Go语言学习Part3:struct、slice和映射
查看>>
Go语言学习Part4-1:方法和接口
查看>>
Leetcode Go 《精选TOP面试题》20200628 69.x的平方根
查看>>
Leetcode C++ 剑指 Offer 09. 用两个栈实现队列
查看>>
Leetcode C++《每日一题》20200707 112. 路径总和
查看>>
云原生 第十一章 应用健康
查看>>
Leetcode C++ 《第202场周赛》
查看>>
云原生 第十二章 可观测性:监控与日志
查看>>
Leetcode C++ 《第203场周赛》
查看>>
云原生 第十三章 Kubernetes网络概念及策略控制
查看>>
《redis设计与实现》 第一部分:数据结构与对象 || 读书笔记
查看>>
《redis设计与实现》 第二部分(第9-11章):单机数据库的实现
查看>>
算法工程师 面经2019年5月
查看>>
搜索架构师 一面面经2019年6月
查看>>
稻草人手记
查看>>
第一次kaggle比赛 回顾篇
查看>>
leetcode 50. Pow(x, n)
查看>>
leetcode 130. Surrounded Regions
查看>>
【托业】【全真题库】TEST2-语法题
查看>>