从源码层面学习Spring中AOP代理拦截的处理过程

Spring中AOP源码深度解读_程序员李哈的博客-CSDN博客目录为什么Spring中存在AOP什么是面向切面什么是动态代理AOP源码解读总结为什么Spring中存在AOP凡是学一样东西,要知其先后。对于Spring来说,其中2大核心IoC和AoP。对于IoC来说控制反转,对于项目中的类(bean)你直接交给Spring就行了,你想使用直接从我这里拿。当项目不断的更新迭代,需要在原有的基础上不断的增加逻辑。可能没有AoP之前,对于程序员来说可能会使用代理模式来对其解耦,此时又有啥静态代理,动态代理的。那么,对于Spring的开发人员https://blog.csdn.net/qq_43799161/article/details/124131569?spm=1001.2014.3001.5502

上篇文章,非常仔细的讲解到AOP的一些概念的理解,和Spring中AOP如何创建代理,并且从反编译字节码中推理出代理方法的MethodInterceptor方法拦截。所以此篇文章继续讲解方法拦截的详细步骤,并且也会对上篇文章的一些点做回顾。

案例代码:

@Component
public class Test {

    public void test(){
        System.out.println("这是test的内容");
    }
    
}


@Aspect   // 声明这是一个切面
@Component
public class Section {         // 切面

    @Before(value = ("execution(* com.liha.bean.Test.test(..))"))       // 切点
    public void testProxy(){
        System.out.println("在之前");      // 通知
    }

}

 这样就可以生成代理类的字节码文件了,然后通过IDEA反编译。

源码讲解

所以看到0号方法拦截的具体逻辑。

@Override
@Nullable
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 {
      if (this.advised.exposeProxy) {
         // Make invocation available if necessary.
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      
      // 获取到target的类对象,也就是被代理类
      target = targetSource.getTarget();

      // 获取到被代理类的Class对象。
      Class<?> targetClass = (target != null ? target.getClass() : null);

      // 使用MethodMatcher的matches()方法对当前方法做切点的匹配,
      // 因为当前代理类中方法可能存在需要被切面增强的,也存在没有被增强的
      // 所以这里就通过ProxyFactory这个Advised的子类获取到所有的切点+通知,
      // 上篇文章有介绍在代理之前把当前代理类中对应的所有切点+通知给放入到ProxyFactory中
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;
      
      // 此时我们要明白,当前类已经是一个代理类了,所有的方法都会走方法拦截
      // 所以这里的判断,意思就是判断当前方法有没有被切面做增强,也就是判断上面匹配出来的结果
      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
         
         // 这里通过反射+Cglib的FastClass执行没被代理的方法
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = methodProxy.invoke(target, argsToUse);
      }

      // 创建一个CglibMethodInvocation对象,将当前的类信息和切点信息给传入,后面会介绍这个类的意义
      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. 获取到被代理类的对象和Class对象
  2. 根据原有方法和Class对象,然后遍历当前代理工厂中的所有切点,然后通过getInterceptorsAndDynamicInterceptionAdvice()方法,方法中使用
    MethodMatcher的matches()方法来做匹对,最后获取到当前方法的所有切点+通知。
  3. 因为当前代理类可能存在被切面做增强的方法,还有存在普通方法,所以这里通过当前方法和MethodMatcher的matches()方法做匹对得出所有切点+通知。如果为空就代表当前方法不需要被切面增强,也就是此方法是普通方法,就直接反射+使用Cglib的FastClass执行普通方法。
  4. 如果当前方法是被切面做增强了,也就是切点+通知有1个或者多个。此时就创建CglibMethodInvocation对象,并且把所有的类信息和代理类信息给传入。

CglibMethodInvocation对象好比就是Aop中真正执行切面增强逻辑+原本方法逻辑的调度者。内部使用递归的方式,把我们的切点+通知包装成一个链来递归处理。因为在Aop中通知,分为前后通知、环绕通知、异常通知、返回值后通知。所以这里就是把他们串成一个链来处理。并且这些通知也在前面的操作以及解析完毕,目前就只剩执行了。所以我们看到proceed()方法。

@Override
@Nullable
public Object proceed() throws Throwable {
   
   // currentInterceptorIndex的初始值为-1,可以理解为索引下标
   // this.interceptorsAndDynamicMethodMatchers就是我们的切点+通知,前面解析成一个List集合
   // 在我的案例里是存在一个Before的通知,但是在Aop中会自带一个切点+通知,所以我的案例是存在2个 
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
     // 能进到这个if中,就代表要执行目标方法,也就是被增强的方法逻辑
     return invokeJoinpoint();
   }

   // 这里取List集合中的值,也就是取切点+通知
   Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

   // 这边逻辑,笔者不是特别清楚,但是我有追踪到创建处,
   // 也就是通过方法来匹对切点的时候会对其做判断,但是永远返回的为false,所以这里是不存在的,所以直接看到else代码块中
   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 {
         // Dynamic matching failed.
         // Skip this interceptor and invoke the next in the chain.
         return proceed();
      }
   }

  // 所以直接来到else代码块中
   else {
      // 执行通知的逻辑了。 我项目中是before,但是笔者会把其他的通知也展示出来,并且说明区别
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

 这里也就是通过索引来控制,通知的逻辑和目标方法的逻辑前后执行。然后每次递归就会取到切点链中的数据,除非第一个if已经通过了,也就是执行了目标方法了。就代表切点链已经遍历完毕了(但是并没有执行完所有的切点链(因为是递归的),因为还有后置,环绕之类的通知)。

所以我们直接看到不同的通知的invoke执行的逻辑区别。

前置通知:

 后置通知:

返回值后通知 

其他的就不一一介绍了,大家可以看invoke方法的各种子类的实现。

 大家一定要思考是递归的场景,并且可以debug来走N遍流程理解,并且可以根据IDEA的debug的工具带有栈帧功能快速定位。

总结

没啥总结,这里稍微复杂就是递归这块,希望大家使用debug好好理解~!

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值