生活小妙招之SpringAOP的核心原理

250 阅读7分钟

前言:我也不知道别人能不能看懂,反正我拿来复习是够了。

MethodInvocation

调用链对象,典型的实现是:ReflectiveMethodInvocation。用于执行proceed方法一次开始执行调用链。另外还有一个作用,就是为动态通知调用提供目标方法的参数。具体来讲,即如果你想对某一个目标方法进行增强,需要用切面包裹切点和通知。切点使用“表示增强时机”的注解(比如@Before)配合切点表达式完成,通常如这样:

@Before("execution(* foo())")
public void before(){}

表示对所有类下的foo方法进行增强,具体增强方法为注解修饰的“通知”。这种做法叫做“静态通知调用”。即增强方法和目标方法没有什么参数上的联系。动态通知调用指的是通知方法可以获取到目标方法的参数。我们可以这样写:

@Before("execution(* foo(..)) && args(x)")
public void before(int x){}

我们之前提到的MethodInvocation还有一个作用就是会将同一个目标对象的所有增强逻辑用到的方法参数存到一个Map中,叫做userAttributes。

对于这一节你需要记住的就是MethodInvocation的两个作用。之后的讲解会让你完全理解该对象的。

调用链是如何执行的

在此首先需要明白什么是责任链设计模式,你需要自己实现或者cv一份代码跑出结果,网上一找一大堆。责任链其实就是一个个的拦截器,在目标方法的前面和后面依次执行。

在AOP中,每一个切点修饰的方法会被转化为一个Advice对象,即增强逻辑。比如:AspectJMethodBeforeAdvice,AspectJAfterAdvice,AspectJAfterThrowingAdvice等,都实现了AbstractAspectJAdvice抽象类,他们就是对应@Before、@Around、@After、@AfterThrowing等注解修饰的方法。但这几个类有两种不同的分类,即:

  1. 除了AspectJMethodBeforeAdvice类,其他的类都实现了MethodInterceptor接口。

  2. 除了AspectJMethodBeforeAdvice有一个before方法看起来是增强的逻辑位置,其他几个类都有一个共通的invoke方法。更加仔细的你会发现这个invoke方法符合这样的逻辑步骤

    try {
        //用于递归调用
       return mi.proceed();//mi即ReflectiveMethodInvocation对象
    }
    finally {
        //执行本advice增强方法而非目标方法。getJoinPointMatch()即是在
        //从MethodInvocation对象中拿到上一节说的目标方法的参数
       invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
    

    如果你熟悉责任链设计模式,你就知道了这样写代码最后增强都会依次发生在目标方法的执行之后。目标方法会在某一个递归执行mi.proceed()前判断条件不满足(条件就是执行递归的次数和拦截器的个数的一个关系)时退出循环递归开始执行目标方法,即foo方法。当然也是反射进行调用的。

    我们回到ReflectiveMethodInvocation的proceed方法,第一行就是递归的结束条件:

    其中调用的invokeJoinpoint()方法即反射调用目标方法。

好了,其实责任链的思想在我们分析了上述内容之后已经渐渐清晰起来,我想你自己也能看懂ReflectiveMethodInvocation的proceed方法的运行逻辑。这其实就是AOP的核心思想。总体来讲就是一个叫做ProxyFactory的工厂创建代理对象,然后代理对象会创建ReflectiveMethodInvocation对象,并调用proceed方法开启责任链的递归调用,对目标方法进行增强。这就是完整步骤了。

其实我们还剩下一个步骤没讲清,那就是目标方法的前置增强是如何实现的。AspectJMethodBeforeAdvice的before方法中也没调用proceed方法,这样就没办法递归了啊,事已至此,不得不提一下适配器设计模式。

关于AOP中的适配器

其实Spring中有好多适配器,适配器的唯一作用就是进行版本兼容,在代码层面上来讲就是接口之间的兼容。因为Spring版本更新了很多次了,不免老版本有一些没考虑到的事情在新版本进行改进与优化,但你不能直接把老接口的内容或继承关系给改了,这是不好的。比较好的做法是搞一个想要老接口兼容的新接口的实现类,把老接口的实现给包装进去作为成员变量,这个新接口的实现类就叫做老接口的适配器,你仔细想想,这样新接口还可以使用老接口的所有功能,并且能在此基础上进行扩展。这种适配器的实现就叫做对象适配器。还有一种叫做类适配器,说白了就是不引入成员变量了,而是让适配器干脆继承老接口的实现,这样也能调用老接口的所有方法并在此基础上进行扩展。很容易理解。

AspectJMethodBeforeAdvice当初设计的时候也是没考虑那么多,因此这个before方法没有MethodInvocation参数。为了完成递归,需要引入适配器:MethodBeforeAdviceAdapter。核心方法参见:

public MethodInterceptor getInterceptor(Advisor advisor) {
   MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
   return new MethodBeforeAdviceInterceptor(advice);
}

这个适配器的实现如果像刚才我提到的适配器的实现好像套不上去是哪种类型的适配器,但你仔细看该方法最后返回了MethodBeforeAdviceInterceptor对象,并将AspectJMethodBeforeAdvice对象包装了进去,我们点进该类MethodBeforeAdviceInterceptor看一下,里面有一个成员变量即advice对象,斌且还有我们熟悉的invoke方法,即对旧接口AspectJMethodBeforeAdvice的before方法进行了扩展。这次是在proceed的前面调用了before,因此一定会在目标方法执行前进行前置增强。我们同时也得出结论:该适配器即对象适配器

关于@Around

我们都知道,被@Around修饰的方法就是环绕通知,是最强大的一种增强,即前后都可以增强。经过上面的分析,你是不是知道了为什么我们在使用环绕通知的时候需要手动调用proceed方法了呢?其实就是为了让递归能顺利进行下去。

具体来讲。如果最后递归的那个拦截器是个AspectJAroundAdvice(之所以一直说拦截器,因为这些通知都是实现了MethodInterceptor接口,AspectJMethodBeforeAdvice也会被适配器转化为实现了该接口的MethodBeforeAdviceInterceptor)的话。递归退出的条件还不满足会执行该拦截器的invoke方法,但是你在执行你自己调用的proceed的时候,又会去判断一次递归的退出条件是否满足,这次看满足退出条件了。因此反射执行完目标方法后退出proceed方法栈,接着执行后面的环绕通知方法。

补充

ExposeInvocationInterceptor

该拦截器是自动会添加到拦截器链中的。作用就是通过ThreadLocal将该线程中的ReflectiveMethodInvocation对象实例放到ThreadLocalMap中。目的我们之前也提到了,因为ReflectiveMethodInvocation存储了所有通知方法的方法参数信息,在动态通知调用的情况会用到,因此其他拦截器在执行通知方法时就会从当前线程取出ReflectiveMethodInvocation,拿到里面的map进行查找。对应的位置就在我们提过的invokeAdviceMethod方法里面。

AnnotationAwareAspectJAutoProxyCreator

这里默认你必须熟悉的Bean处理器的概念。它既是Bean后处理器。从名字你可以看出它是自动创建代理对象的。本文重点讲解的所有内容是上述蓝字的流程中的重要部分。而蓝色所有的执行内容就是AnnotationAwareAspectJAutoProxyCreator进行的。它的执行时机是Bean的依赖注入之前或者是初始化之后。这里的创建时机就与循环依赖的解决有关了。这已经和本文的内容没什么关系了,之后再说,再会。