AOP的源码我计划分为四部分来学习

  1. 通知方法优先级源码
  2. 切面执行先后顺序优先级
  3. 判断哪些方法需要生成代理对象 + 动态代理对象的生成
  4. 通知方法的执行(拦截器的拦截)

在aop中,众所周知,通知方法的优先级是:Around > Before > After > AfterReturning > AfterThrowing:
这篇笔记主要记录为什么优先级是这样子的,原理是 什么样的

通知方法优先级

org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory
static {/*** 这里应该是定义aop通知的先后顺序** 从前到后,优先级依次降低*/Comparator<Method> adviceKindComparator = new ConvertingComparator<>(new InstanceComparator<>(Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),(Converter<Method, Annotation>) method -> {AspectJAnnotation<?> annotation =AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);return (annotation != null ? annotation.getAnnotation() : null);});Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
}

在ReflectiveAspectJAdvisorFactory中,有一个静态代码块,定义了通知方法的优先级,主要是声明了一个comparator对象
其实这个比较器,可以看下是在哪里被调用的,搜索整个类,会发现,是在org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethods
方法中被调用的,而getAdvisorMethods方法又是在org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisors 这个方法中被调用,我们先说到这里,先来看下具体的处理逻辑

/*** 获取到切面的所有通知方法,并根据定义好的优先级,对通知方法进行排序;* 需要注意:这里是对一个切面中的通知方法先进行排序* 并将排好序之后的切面返回* @param aspectInstanceFactory the aspect instance factory* (not the aspect instance itself in order to avoid eager instantiation)* @return*/
@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();validate(aspectClass);// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator// so that it will only instantiate once.MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new ArrayList<>();/*** 1、getAdvisorMethods()该方法获取到的所有advice通知方法已经是排好序的通知方法了,只需要按照优先级* 往集合中add即可** 这里获取到的所有方法是切面中除了@PointCut修饰的所有方法*/for (Method method : getAdvisorMethods(aspectClass)) {/***/2、advisors.size():这个参数就是通知方法的优先级,这里用advisors的长度作为优先级,用的比较妙* 因为这个for循环的方法,是排序之后的方法,所以依次从0开始,优先级最高,后面优先级逐渐变低*/Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);if (advisor != null) {advisors.add(advisor);}}// If it's a per target aspect, emit the dummy instantiating aspect.if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}// Find introduction fields.for (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;
}

上面这个方法我们姑且不说是在哪里被调用的,我们只看对应的处理逻辑
1、根据切面获取到所有的通知方法,并进行排序
2、按照排好序之后的通知方法,依次生成对应的advisor对象,然后返回

/*** 这个方法,会获取到切面所有的通知方法,然后进行排序* @param aspectClass* @return*/
private List<Method> getAdvisorMethods(Class<?> aspectClass) {final List<Method> methods = new ArrayList<>();ReflectionUtils.doWithMethods(aspectClass, method -> {// Exclude pointcuts/*** 如果没有被pointCut注解修饰,就添加到methods中,最后统一根据优先级进行排序* 这里之所以这么判断,和入参的class有关系,这里入参的class一定是一个切面*/if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {methods.add(method);}});methods.sort(METHOD_COMPARATOR);return methods;
}

在getAdvisors方法中的第二点,这里有一个很细节的点,是根据advisors这个list数组的长度来确定切面优先级的,为什么可以这么做呢?因为所有的通知方法是按照优先级排好序的,所以这里直接以数组长度作为优先级,去初始化Advisor对象

何时被调用

说到这里,需要先知道,aop的话,需要使用一个注解@EnableAspectJAutoProxy,在该注解中,注入了一个后置处理器AnnotationAwareAspectJAutoProxyCreator

在该后置处理器的org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
方法,首先这个方法是在初始化bean的生命周期中,第一个被调用的后置处理器方法,如果该方法返回的是一个代理对象,就不会去调用后续的流程,我们需要先知道被调用的时机

在该后置处理器方法中,1、会判断bean是否需要被代理2、对切面进行解析
比较关键的是第二步:

org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#shouldSkiporg.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisorsorg.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

在buildAspectJAdvisors方法中,是解析我们声明的切面的关键

/*** Look for AspectJ-annotated aspect beans in the current bean factory,* and return to a list of Spring AOP Advisors representing them.* <p>Creates a Spring Advisor for each AspectJ advice method.* @return the list of {@link org.springframework.aop.Advisor} beans* @see #isEligibleBean* 这个方法很重要,这里和aop有关系* 在第一个后置处理器执行的时候,会调用到这里* 然后在这里找到所有的切面以及对应的通知方法** aspectBeanNames = aspectNames:存放的是所有加了@Aspect注解的beanName* advisors:存放的是所有通知方法(多个切面的所有通知方法)* advisorsCache:这个map中,key是切面的beanName,value是一个list,存储的是当前切面对应的所有通知方法**/
public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;/*** 在第一次进入到这里的时候,这里的aspectBeanNames是null,* 会执行下面的解析逻辑,解析之后,会将aspectBeanNames进行赋值* 第二次再调用该方法的时候,就不会解析了,而是直接从缓存中获取,这里获取到的所有advice方法* 是已经排好序的(按照通知方法优先级进行排序的)*/if (aspectNames == null) {synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new ArrayList<>();aspectNames = new ArrayList<>();/*** 2.这里会找到所有的对象,Object的实现类,其实就是所有的bean了* 然后进行遍历*/String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {if (!isEligibleBean(beanName)) {continue;}// We must be careful not to instantiate beans eagerly as in this case they// would be cached by the Spring container but would not have been weaved./*** 2.1 根据beanName获取到对应的beanType*/Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}/*** 2.2 判断bean是否添加了@Aspect注解,如果添加了,就表示当前bean* 是一个切面,放入到一个变量中*/if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);/*** 2.3 会尝试找到当前切面所有的通知方法,这里返回的通知方法,是已经排过序的,但是只是是* 通知方法的排序,并没有对切面做排序,因为这里入参就是切面的beanName*/List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}else {// Per target or per this.if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName +"' is a singleton, but aspect instantiation model is not singleton");}MetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}/*** 3.将这个临时变量付给aspectBeanName*/this.aspectBeanNames = aspectNames;return advisors;}}}if (aspectNames.isEmpty()) {return Collections.emptyList();}/*** 4.这里是获取所有切面中的所有通知方法*/List<Advisor> advisors = new ArrayList<>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);}else {MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}

这里2.3就是调用查找切面中所有通知方法的点
对于这个方法来说,主要是完成了以下的操作
1、从beanDefinitionMap中找到所有的bean
2、然后依次解析,判断是否有添加@Aspect注解
3、对添加了切面的注解的bean,进行解析,在注释中的2.3部分,会调用getAdvisors方法,去解析所有的通知方法,然后生成对应的advisor对象
4、然后会将解析到的advisor对象存入到内存的map中,同时会将切面存入到list集合中,private volatile List aspectBeanNames; 这样的话,下次再调用到该方法的时候,就无需再次解析了

结论

所以,通过上面的分析,可以得到以下结论
1、在每个bean被初始化的时候,会先调用第一个后置处理器方法,解析所有的切面,解析完成之后,会存入到内存中
2、在下次再次调用到该方法的时候,就无需再次解析,直接从内存中获取即可
3、在解析通知方法的时候,会指定切面的优先级

SpringAop源码一:通知方法优先级相关推荐

  1. SpringAop源码(六)- 标签aspectj-autoproxy实现分析

    目录 1.自定义标签.解析器的加载和调用时机 2.AspectJAutoProxyBeanDefinitionParser 3.总结 1.自定义标签.解析器的加载和调用时机 之前分析过EnableAs ...

  2. React源码解毒 - render方法解析

    render方法解析 要将react元素渲染到页面当中,分为了两个阶段: render阶段 和 commit阶段. render阶段:由协调层负责的阶段 在这个阶段当中要为每一个react元素构建对应 ...

  3. java 注释工具栏_eclipse/intellij idea 查看java源码和注释方法

    工作三年了,一直不知道怎么用IDE查看第三方jar包的源码和注释,惭愧啊!看源码还好些,itellij idea自带反编译器,eclipse装个插件即可,看注释就麻烦了,总不能去找api文档吧!现在终 ...

  4. php源码如何使用教程,php源码的使用方法是什么?

    php源码的使用方法是:首先安装phpnow环境套件包:然后把所有的源码复制到套件安装目录下的htdocs文件夹里:最后导入数据库,并确定数据库连接参数如密码等是正确的即可. php源码的使用方法是: ...

  5. win10最新下载编译Android源码的简易方法分享(已亲测)

    win10最新下载编译Android源码的简易方法分享(已亲测) 准备至少150G的硬盘空间 win+R 输入control 打开程序与功能-启用或关闭Windows功能 勾选"适用于Lin ...

  6. 论文阅读笔记——基于CNN-GAP可解释性模型的软件源码漏洞检测方法

    本论文相关内容 论文下载地址--Engineering Village 论文阅读笔记--基于CNN-GAP可解释性模型的软件源码漏洞检测方法 文章目录 本论文相关内容 前言 基于CNN-GAP可解释性 ...

  7. SpringAOP源码解析总结

    问题一.何时生成代理bean?? 在getBean()过程中,进行了bean的实例化.依赖注入.初始化后,通过bean后处理器进行生成代理类. 在getBean()过程中的AbstractAutowi ...

  8. glib 2.0 arm linux,glib源码安装使用方法

    glib库是GTK+和GNOME工程的基础底层核心程序库,是一个综合用途的实用的轻量级的C程序库,它提供C语言的常用的数据结构的定义.相关的处理函数,有趣而实用的宏,可移植的封装和一些运行时机能,如事 ...

  9. mybatis源码分析(方法调用过程)

    十一月月底,宿舍楼失火啦,搞得20多天没有网,目测直到放假也不会来了... 正题 嗯~,其实阅读源码不是为了应付面试,更重要的让你知道,大师是怎样去写代码的,同样是用Java,为啥Clinton Be ...

  10. Eclipse中【Maven下载时,下载源码】设定方法

    目录 ■Eclipse中设定方法 ■效果 ■扩展 ●2.14.1 存在漏洞 JndiLookup.java AbstractLookup.java ​ ■Eclipse中设定方法 Maven Down ...

最新文章

  1. php 去掉字符串的逗号,php 怎么去掉字符串最后一个逗号
  2. 海南师范大学计算机设计大赛证书,我校品牌VI设计作品在中国大学生计算机设计大赛海南省赛中获得一等奖...
  3. java 把URL中的中文转换成utf-8编码
  4. python中如何定义函数的传入参数是option的_python – 当使用@ click.option将命令行参数传递给函数时,如何返回值?...
  5. C语言 回调函数 callback - C语言零基础入门教程
  6. JSP之内置对象、作用域
  7. linux设置组配置额步骤,linux 中磁盘配额设置
  8. t-sql执行结果_用于记录流程执行的T-SQL设计模式
  9. 【暴力枚举】LeetCode 78. Subsets
  10. 手机远程访问HTML5,如何使用手机连接远程桌面?
  11. 1024程序节|Android框架之一 BRVAH【BaseRecyclerViewAdapterHelper】使用demo
  12. 系列学习 Swagger 之第 2 篇 —— SpringCloud Gateway 整合 Swagger(完结)
  13. 天翼网关F452超级密码获取(亲测有效)
  14. (八)【模电】(基本放大电路)场效应管及其放大电路
  15. 与dalao学校的联shou考wan(11.2)(dp+乱搞+树形dp+期望)
  16. 滴滴秋招提前批正式开始,现在投递免笔试
  17. 手势控制鼠标的操作(不指屏幕实现简单触屏)
  18. Java编译错误与运行时错误区别
  19. springboot集成graphql(一)
  20. 论文理解【IL - 数据增广】 —— Adversarial Imitation Learning with Trajectorial Augmentation and Correction

热门文章

  1. Training_model(2)
  2. java 文件上传终止_java文件上传
  3. mapjoin的使用方法以及注意事项
  4. SSD,单次多框检测器Single Shot Multibox Detector,超越YOLO和Fast-RCNN
  5. python实现mapreduce求平均值
  6. NLP系列(2)_用朴素贝叶斯进行文本分类(上)
  7. oracle ebs 接收数量,[zz]Oracle EBS API: 库存数量查询API示例
  8. 147.有序数组的TwoSum(力扣leetcode) 博主可答疑该问题
  9. 计算机主机光驱弹不出来怎么办,win7系统电脑按下光驱按钮托盘就是弹不出来怎么办...
  10. php socket 执行,PHP异步调用socket实现代码