AOP流程源码分析-请求调用全流程

本次会将剩下的AOP 内容全部聊一下,其实就是剩一个请求调用全流程了,后续Spring 系列文章就还剩boot 和cloud 呢,boot 在我面试前应该还能讲一讲,但是cloud 因为这些年一直在保险行业混,现在也不是很清楚,分布式现在能记住的就是dubbo 体系的了,这个只能后面再说了,如果面试这个必须上的话,我就再看看cloud 然后整理一下相应文章,不行的话,只能等面试结束了,不过今年应该能写完。

源码分析

说了一些废话,现在就开始本次的源码分析了,老司机发车,坐稳。

上篇aop 的文章讲了代理的创建(一共就两篇aop,不用我再把链接标出来吧)分为两种方式cglib 和jdk,我们一般情况下使用的还是jdk 创建的,cglib 在不熟悉的情况下,不建议使用。我们本次要讲的也是jdk 生成代理后,请求调用的全流程。

首先要看到就是JdkDynamicAopProxy 对象,代理对象是需要继承了InvocationHandler 对象的,那么就一定会重写invoke 方法(后面关于dubbo 服务调用调用流程的时候也会讲到这个,这里要注意看下)当请求调用被代理对象的方法时,就会走到这个invoke 方法,所以我们直接跟进该方法。

拦截器链、被代理对象实例、执行器的获取与构建-invoke方法

跟进方法。

首先这里会忽略一下method 是否为equals、hashCode 等Object 的方法,因为这些基本方法是不会被代理的。

然后就是获取被代理对象的实例

// 2.拿到我们被代理的对象实例
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);

再然后就是获取我们之前解析出的拦截器链

// 【*】3.获取拦截器链:例如使用@Around注解时会找到AspectJAroundAdvice,还有ExposeInvocationInterceptor
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

到这里就不再关注其它的内容了,主要看拦截调用。

这里首先构建一个ReflectiveMethodInvocation 对象,这里我理解是一个执行器对象,然后直接调用该对象的proceed 方法。

// 6.如果存在拦截器,则创建一个ReflectiveMethodInvocation:代理对象、被代理对象、方法、参数、
// 被代理对象的Class、拦截器链作为参数创建ReflectiveMethodInvocation
MethodInvocation invocation =
  new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 7.触发ReflectiveMethodInvocation的执行方法(逐一调用chain中的每一个拦截方法的proceed)
retVal = invocation.proceed();

调用拦截器和执行被调用对象的方法-proceed方法

跟进proceed 方法。

然后就会看到一段很有意思的逻辑,这里我解释一下,这里的currentInterceptorIndex 属性的值是 -1,而和它对应的集合就是拦截器链,也就是advisors 的集合的大小 - 1,然后invokeJoinpoint 方法就是执行被代理对象真在被调用的方法。

继续下面就是根据拦截器链中的下标索引获取具体的拦截器,记住这段代码执行完之后currentInterceptorIndex 属性的值就会 +1。这里先记住两点:1、当该属性值等于拦截器链大小的时候,就会执行被代理对象的正在调用的方法;2、只要获取了具体拦截器,currentInterceptorIndex 属性的值就会 +1。

// 1.如果所有拦截器都执行完毕(index是从-1开始,所以跟size - 1比较),则直接使用反射调用连接点(也就是我们原本的方法)
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
    
    
  return invokeJoinpoint();
}

// 2.每次调用时,将索引的值递增,并通过索引拿到要执行的拦截器
Object interceptorOrInterceptionAdvice =
  this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

然后我们接着往下看,直接看拦截器的调动。

return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);

目前我们的拦截器还是上篇文章中的配置,也就是三个拦截器,环绕、前置、后置,但是spring 会默认再给我们加一个最初的拦截器,这个我们暂时不用看。

image-20220428215745492

对了拦截器的顺序就是环绕、前置、后置,至于同级拦截器之前应该是看配置解析的顺序,先配置的先执行,但是后置相反,至于为什么,我们继续看代码。

拦截器对象:AspectJAroundAdvice对象

我们现在直接跟到环绕拦截器对象:AspectJAroundAdvice 对象的invoke 方法里面。

这里会先封装两个对象,然后直接调用invokeAdviceMethod 方法。这个方法很绕,但是目的就是为了调用我们之前编写好的环绕拦截的方法。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    
    
  // 1.这边的mi就是我们的ReflectiveMethodInvocation,
  // ReflectiveMethodInvocation实现了ProxyMethodInvocation接口,所以这边肯定通过校验
  if (!(mi instanceof ProxyMethodInvocation)) {
    
    
    throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
  }
  // 2.将mi直接强转成ProxyMethodInvocation,mi持有代理类实例proxy、被代理类实例target、被代理的方法method等
  ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
  // 3.将pmi封装成MethodInvocationProceedingJoinPoint(直接持有入参mi,也就是ReflectiveMethodInvocation的引用)
  ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
  // 4.拿到pointcut的表达式
  JoinPointMatch jpm = getJoinPointMatch(pmi);
  // 5.调用增强方法
  return invokeAdviceMethod(pjp, jpm, null, null);
}

我们回到环绕拦截的方法,在这里会有一个入参ProceedingJoinPoint 对象,这个就是我们上面ReflectiveMethodInvocation 对象的封装,这里调用的proceed 方法跟我们上面的调用是一样的。

这里就意味着目前环绕拦截没有执行完的情况下,又会回到proceed 方法中走下一个拦截器。注意如果没有这个调用,就没有后面所有拦截器的调用了。

@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
    
    
  System.out.println("around advice start");
  try {
    
    
    Object result = proceedingJoinPoint.proceed();
    System.out.println("around advice end");
    return result;
  } catch (Throwable throwable) {
    
    
    throwable.printStackTrace();
    return null;
  }
}

前置拦截器:MethodBeforeAdviceInterceptor对象

然后我们再看看前置拦截器。

前置拦截器:MethodBeforeAdviceInterceptor 对象,内容比较简单,就是执行我们之前对应编写的前置方法,然后继续回调proceed 方法,等于是又回到下个拦截器的调用。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    
    
  this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
  return mi.proceed();
}

后置拦截器:AspectJAfterAdvice对象

继续看后置拦截器:AspectJAfterAdvice 对象,这里同样简单,不过首先就是回到proceed 方法,当下一个拦截器调用成功之后,再执行自己的方法调用。

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    
    
  try {
    
    
    return mi.proceed();
  }
  finally {
    
    
    invokeAdviceMethod(getJoinPointMatch(), null, null);
  }
}

流程小结

现在整理一下流程首先是spring 提供的拦截器调用,当该拦截器执行完成之后,调用了proceed 方法,走下一个环绕拦截器,然后由环绕拦截,调用proceed 方法走到前置拦截器,再由前置拦截器调用proceed 方法走到后置拦截器。注意我这里的拦截器已经走完了,那么之前的currentInterceptorIndex 属性值现在就应该是3了,同时拦截器链的大小 -1 也是3,那么就会走上面说的invokeJoinpoint 方法,这样就走了被代理对象的正在调用的方法,同时注意调用完成之后,下面是获取不到拦截器了的,那么意味在后置拦截器调用的proceed 方法才走完,目前是后置拦截器中继续走,这样才会走后置拦截器的拦截方法。

说了这么多,有点绕,所以简单一点描述:proceed 方法调用拦截器1(标识第一次调用,后续累计) -> spring 默认拦截器 -> proceed 方法调用拦截器2 -> 环绕拦截器方法 -> proceed 方法调用拦截器3 -> 前置拦截器方法 -> proceed 方法调用拦截器4 -> 后置拦截器方法 -> proceed 方法调用拦截器5 ->注意这里已经没有拦截器了,所以走了invokeJoinpoint 方法。

流程没有走完,还有回去:proceed 方法调用拦截器5 -> 后置拦截器方法 -> proceed 方法调用拦截器4 -> 前置拦截器方法 -> proceed 方法调用拦截器3 -> 环绕拦截器方法 -> proceed 方法调用拦截器2 -> spring 默认拦截器 -> proceed 方法调用拦截器1-> 最后才是拦截器1的结束。

这样我们就能知道为什么,后置拦截器,配置在前面的反而后执行,因为后置拦截器,没有先执行自己的拦截方法,而是调用了下一个拦截器,所以配置在最后的后置拦截器要比前面的后置拦截器,先执行。

总结

调用流程其实并不是很复杂,就是比较绕,看明白之后会发现这个还是比较有趣的,没有使用循环,但是也实现了循环的效果,而且更加高明,这就是任务链的设置模式。

本次就在这里结束了,内容比之前要少很多,因为前面已经做好了全部的铺垫,这里很多都是引用,后续的spring 文章呢,看情况吧,下阶段还是要先看下jdk 的源码,这快的面试问题也不少,后续如果面试上了,我会将面试题在做一个系列,先这样。终点站到了,下车。

附录Spring 源码分析系列文章

IOC

时间 文章
2022-03-09 Spring的基本概念和IOC流程的简述
2022-03-11 IOC流程解析-BeanFactory的创建
2022-03-14 IOC流程解析-BeanFactoyPostProcessor和BeanPostProcessor
2022-03-15 IOC流程解析-实例化和初始化
2022-03-17 IOC流程解析-循环依赖

AOP

时间 文章
2022-03-19 Spring的基本概念和IOC流程的简述
2022-03-20 AOP流程源码分析-请求调用全流程

猜你喜欢

转载自blog.csdn.net/qq_39339965/article/details/124489143