【Spring AOP源码分析二】上一篇中,我们已经找到了AOP的源码入口,我们今天继续分析下面的代码,不过在此之前我们需要看下Spring中如何使用切面的,以便于我们理解我们的源码 。代码如下:
package com.younger.web.aspect;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;/** * 日志切面 */@Component@Aspectpublic class LogAspect implements Ordered {/*** 定义切点*/@Pointcut("execution(* com.younger.web.service.impl.system(..))")public void pointcut(){}private void pointCut1(){}/*** 前置通知*/@Before("pointcut()")private static void before(){System.out.println("---前置通知---");}/*** 后置通知*/@AfterReturning("pointcut()")public void afterReturning(){System.out.println("---后置通知---");}@Overridepublic int getOrder() {return 1;}} 我们在项目中一般是通过@Aspect注解标记一个切面类的,在上面这个切面中实现了ordered接口,spring 会通过ordered的顺序来执行切面的,ordered值越小,优先级越高 。那Spring是如何通过@Aspect注解找到切面类的呢?接下来我们就开始继续分析源码吧,上篇文章我们分析到了这个位置:
首先如果这个bean已经被处理过的话,那就直接返回,如果这个bean不需要被增强,就直接返回,如果这个bean被指定为不需要进行代理,也直接返回,就是一些常规校验 。接着就会调用方法getAdvicesAndAdvisorsForBean(),我们到方法中看下:
这个方法主要就是调用findEligibleAdvisors()方法来获取合适的增强 。我们到这个findEligibleAdvisors()方法看下:
在这个方法主要有两个主要的步骤:
1、调用findCandidateAdvisors方法获取所有的增强 。
2、调用findAdvisorsThatCanApply方法找到与当前bean相匹配的增强 。
我们先看下findCandidateAdvisors()方法是怎么获取所有的增强的,代码如下:
我们发现,在这个findCandidateAdvisors()方法中,首先调用了super.findCandidateAdvisors()方法来获取xml中配置的增强,也就是说我们不仅可以通过@Aspect注解方式配置的AOP,也可以使用xml方式配置AOP的 。接着又调用了一个buildAspectJAdvisors()方法,为@Aspect注解标注的切面类构建增强,因为我们目前使用的是@Aspect注解的方式,所以super.findCandidateAdvisors()这行代码会返回一个空集合,我们现在来看下buildAspectJAdvisors()方法:
public List
我们可以看到,首先第一次调用这个buildAspectJAdvisors()方法时,这个aspectNames一定是个null,接着就会进入到这个if分支的处理中,首先从IOC容器中获取所有的bean,然后遍历依次处理每个bean 。在处理bean的时候,主要有两个操作:
1、通过advisorFactory.isAspect()找到加了@Aspect注解的类;
2、通过this.advisorFactory.getAdvisors(factory)进一步找到切面类中的增强方法,并将增强方法构建为Advisor 。
那我们就先来看下isAspect()方法是如何来找切面类的,isAspect()的方法代码如下:
可以看到,在isAspect方法中调用了hasAspectAnnotation() 方法,接着又调用了AnnotationUtils.findAnnotation() 。而在方法中传入了Aspect.class参数,我们看一下这个Aspect:
这个类就是我们在切面类上加的那个注解@Aspect 。我们看下这个AnnotationUtils.findAnnotation方法:
其实这个AnnotationUtils.findAnnotation()方法,就是专门用来在指定类上找特定注解的 。
我们继续看下面的流程:
在分析这个构建 Advisors源码之前,我需要知道我们平时在使用AOP时,增强逻辑都会放在切面中一个一个的方法中,那么在为目标方法添加增强逻辑时,当然要拿到切面类中声明的这些方法,这样才能去执行对应的增强逻辑 。所以这里不光要找到切面类,还要获取到切面类中方法,目前为止我们已经找到了切面类,那么下一步当然就是要获取到切面类中的方法,来看下Spring是怎么获取到切面类中方法的:
@Override public List
可以看到,这里首先通过getAdvisorMethods(aspectClass)方法获取切面类中声明的方法,然后调用Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName)依次为每一个方法生成对应的增强Advisor 。
首先我们要看的,就是获取切面类中方法的逻辑了,这个时候我们可以点进去getAdvisorMethods(aspectClass)方法来看下,此时代码如下:
我们可以看到,这里主要调用了ReflectionUtils.doWithMethods()来获取切面类中的方法 。并且我们可以看到调用doWithMethods()方法时需要传入一些参数,那这些参数代表什么意思呢?我们来看下这个doWithMethods()方法的定义:
此时我们看到这个doWithMethods()方法定义了三个参数,分别是Class> clazz、MethodCallback mc和MethodFilter mf,第一个参数就是一个class对象,第二个和第三个参数通过参数名我们可以判断出,第二个参数MethodCallback是一个回调方法,而第三个参数是一个过滤条件 。我们分别来看一下MethodCallback和MethodFilter分别是怎么定义的吧,如下图:
可以看到MethodCallback和MethodFilter的定义中,它们都加了@FunctionalInterface注解,所以MethodCallback和MethodFilter都是函数式接口,我们可以使用Lambda表达式调用它们 。现在我们知道了doWithMethods()方法的第二个参数和第三个参数都是函数式接口,那我们调用doWithMethods()方法时,这三个参数都是怎么指定的呢?我们再回头来看下调用doWithMethods()方法的地方吧,如下图:
首先第一个参数aspectClass,就是指定切面类,第二个参数回调方法的本质,其实就是实现了MethodCallback接口的doWith()方法了,而第三个参数过滤条件的本质,则是实现了MethodFilter接口的matches()方法 。看到这三个参数,我们就大致明白了doWithMethods()方法要做的事情,就是过滤出指定类aspectClass中的方法,过滤条件就是这个ReflectionUtils.USER_DECLARED_METHODS,最后通过回调方法将满足条件的method放到一个List集合中 。我们知道了doWithMethods()方法要做的事情只之后,那现在就来看下它是怎么实现的吧,我们看这里:
首先就是先调用getDeclaredMethods()方法获取切面类中声明的方法methods,接着遍历处理这些method 。
先调用mf.matches(method)过滤出要处理的方法,最后将过滤出来的方法,通过回调逻辑统一放到一个List集合中,我们这里来看一下方法过滤是怎么做的 。我们点进去mf.matches(method)来看下,这个mf大家还记得吗?它其实就是一个函数式接口的实现类,而我们传进来的是ReflectionUtils.USER_DECLARED_METHODS,所以我们现在来看下这个ReflectionUtils.USER_DECLARED_METHODS到底是个啥,如下图:
可以看到,这里直接调用了Method对象的isBridge()方法和isSynthetic()方法来做的过滤,而这个isBridge()方法是用来判断当前方法是否为桥接方法,而isSynthetic()方法是用来判断当前方法是否为合成方法 。简单来说,桥接方法和合成方法是编译器由于内部需要,编译器自己创建出来的方法,而不是我们自己创建的方法 。!method.isBridge() && !method.isSynthetic() 这行代码在当前方法为非桥接方法且非合成方法时会返回true,说白了就是过滤出我们自己创建的方法,排除掉那些编译器内部创建的方法 。那么当mf.matches(method)这行代码执行完毕,究竟能从切面类中过滤出来哪些方法呢?我们这里以日志切面类为例,来看下哪些方法是非桥接且非合成方法吧,首先我们先回顾一下日志切面类LogAspect ,代码如下:
在切面类LogAspect中的pointcut()、 before()、 afterReturning()、getOrder()这四个方法既不是桥接方法,也不是合成的方法,这些方法都是我们自己创建的方法,所以这四个方法执行到mf.matches()方法时,mf.matches()方法返回的都是true 。mf.matches()方法执行完之后,就开始回调执行方法了,如下图:
这里其实直接调用了入参传进来mc的doWith()方法,这个mc实现类是我们自己传递进来的,此时就会回调到下面的逻辑,我们看这里:
其实就是将过滤出来的方法放到一个List集合中,但是我们发现在此之前,还有一个if条件,就是AnnotationUtils.getAnnotation(method, Pointcut.class) == null这行代码,只有当这个条件成立时,才会将方法放入到List集合中 。并且我们还发现在调用getAnnotation()方法时,除了将method传进去之外,还传进去了一个Pointcut.class,这个Pointcut.class又是个什么?我们点进去看下Pointcut.class,代码如下:
这个就是我们的@Pointcut注解,我看下:
在切面类LogAspect的pointcut() 方法上加了一个@Pointcut注解,用来定义切点表达式使用的 。那这AnnotationUtils.getAnnotation(method, Pointcut.class) 方法到底是干嘛的?我们可以先点进去看下AnnotationUtils.getAnnotation()方法的定义,如下图:
我们可以看到这个方法有两个入参,第一个参数是方法对象本身,第二个参数是注解类型,通过上面的注释,我们可以判断出这个getAnnotation()方法,其实就是用来在指定的方法method上获取特定注解的 。那也就是说AnnotationUtils.getAnnotation(method, Pointcut.class) 方法是用来获取方法上的@Pointcut
注解的,那么AnnotationUtils.getAnnotation(method, Pointcut.class) == null又是什么意思呢?首先这个AnnotationUtils.getAnnotation(method, Pointcut.class) 方法什么时候会返回空?当然是这个方法上没有加@Pointcut注解的时候才会返回null,因为此时在这个方法上找不到@Pointcut注解,那当然要返回null,那么此时AnnotationUtils.getAnnotation(method, Pointcut.class) == null的结果为true,那么就会将这个方法添加到到List集合中,其实就是执行下边这行代码:
那如果此时当前方法加了@Pointcut注解呢?比如切面类LogAspect的pointcut() 方法上就加了一个@Pointcut注解 。那此时AnnotationUtils.getAnnotation(method, Pointcut.class) == null的结果就为false,此时就不会将这个pointcut() 方法添加到List结合中 。其实说白了这个条件就是用来排除掉@Pointcut注解的pointcut() 方法的 。以切面类LogAspect为例,在执行完mf.matches()时,由于pointcut()、before()、afterReturning()、getOrder()这四个方法既不是桥接方法,也不是合成方法,所以这四个方法都可以正常通过 。而执行到AnnotationUtils.getAnnotation(method, Pointcut.class) == null这个条件时,会将pointcut()方法给排除掉,因为pointcut()方法上加了@Pointcut注解,所以此时就只剩下before()、afterReturning()、getOrder()这三个方法了,最后就会将这三个方法放入到一个List集合中,然后作为getAdvisorMethods()方法的结果返回 。
我们通过一张流程图总结一下今天的内容:
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
