Spring 源码阅读 52:查找注解配置的切面增强逻辑(2)- 查找增强方法

简介: 本文分析了在获取到基于注解的切面配置类之后,如何从中找到配置增强逻辑的方法,找到后,Spring 会为每一个增强逻辑创建一个 Advisor。

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 51:查找注解配置的切面增强逻辑(1)- 查找配置类

概述

对于 Spring AOP 中通过注解配置的切面信息,AnnotationAwareAspectJAutoProxyCreator 后处理器类中的findCandidateAdvisors方法用来从 Spring 容器中找到所有的切面配置类,并找到其中的增强逻辑。

在上一篇中,重点分析了findCandidateAdvisors方法中调用的buildAspectJAdvisors方法,如何将符合条件的切面配置类找到,本文我们讲分析如何从这些配置类中,将其中的增强逻辑找出来。作为上一篇后续的内容,本文的分析从buildAspectJAdvisors方法中调用的getAdvisors方法作为分析的切入点。

查找增强方法

调用getAdvisors方法是传入的参数,是一个包含了当前 Spring 容器和切面配置类的 Bean 名称的 MetadataAwareAspectInstanceFactory 对象。

MetadataAwareAspectInstanceFactoryfactory=newBeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor>classAdvisors=this.advisorFactory.getAdvisors(factory);

getAdvisors 方法

进入getAdvisors方法。

image.png

方法实现在 ReflectiveAspectJAdvisorFactory类中,代码的结构比较清晰,我们还是分布来进行分析。

Class<?>aspectClass=aspectInstanceFactory.getAspectMetadata().getAspectClass();
StringaspectName=aspectInstanceFactory.getAspectMetadata().getAspectName();
validate(aspectClass);

首先获取了切面配置类的类型信息和 Bean 名称,并对类型信息进行了验证。验证的目的主要是为了保证它是一个标注了@Aspect注解的非抽象类型,切与切面相关的配置信息是符合后续处理的条件的。

// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator// so that it will only instantiate once.MetadataAwareAspectInstanceFactorylazySingletonAspectInstanceFactory=newLazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

然后用 MetadataAwareAspectInstanceFactory 对参数传入的aspectInstanceFactory进行包装,确保它只会被实例化一次。

接下来进入关键的逻辑。

getAdvisorMethods 方法

List<Advisor>advisors=newArrayList<>();
for (Methodmethod : getAdvisorMethods(aspectClass)) {
Advisoradvisor=getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor!=null) {
advisors.add(advisor);
   }
}

通过getAdvisorMethods从切面配置类的类信息中获取一个 Method 列表,从方法名字看,获取到的应该是增强方法的列表。

我们进入getAdvisorMethods查看一下。

// org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethodsprivateList<Method>getAdvisorMethods(Class<?>aspectClass) {
finalList<Method>methods=newArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method-> {
// Exclude pointcutsif (AnnotationUtils.getAnnotation(method, Pointcut.class) ==null) {
methods.add(method);
      }
   }, ReflectionUtils.USER_DECLARED_METHODS);
if (methods.size() >1) {
methods.sort(METHOD_COMPARATOR);
   }
returnmethods;
}

这里面还涉及到了 ReflectionUtils 类中的doWithMethods方法,我们先这个方法。

// org.springframework.util.ReflectionUtils#doWithMethods(java.lang.Class<?>, org.springframework.util.ReflectionUtils.MethodCallback, org.springframework.util.ReflectionUtils.MethodFilter)publicstaticvoiddoWithMethods(Class<?>clazz, MethodCallbackmc, @NullableMethodFiltermf) {
// Keep backing up the inheritance hierarchy.Method[] methods=getDeclaredMethods(clazz, false);
for (Methodmethod : methods) {
if (mf!=null&&!mf.matches(method)) {
continue;
      }
try {
mc.doWith(method);
      }
catch (IllegalAccessExceptionex) {
thrownewIllegalStateException("Not allowed to access method '"+method.getName() +"': "+ex);
      }
   }
if (clazz.getSuperclass() !=null&& (mf!=USER_DECLARED_METHODS||clazz.getSuperclass() !=Object.class)) {
doWithMethods(clazz.getSuperclass(), mc, mf);
   }
elseif (clazz.isInterface()) {
for (Class<?>superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
      }
   }
}

这个方法的参数列表中,除了一个 Class 类型信息之外,还有两个参数,分别是 MethodCallback 和 MethodFilter 类型,这两个类型的源码就不贴在这里了,他们其实都是函数式接口,从名称中也可以看出它们的用途,后续的方法体中遇到了我们再深入分析。

doWithMethods方法中,首先获取到了类型中声明的所有方法,得到一个 Method 列表,然后遍历这个列表。在for循环中,首先通过 MethodFilter 的matches方法对其进行了过滤,然后将方法信息作为参数,调用了MethodCallback 的doWith方法。

我们看一下这里的matchesdoWith的具体实现。

matches方法对应上一步调用doWithMethods方法的第三个参数,也就是ReflectionUtils.USER_DECLARED_METHODS,找到这个常量的定义。

publicstaticfinalMethodFilterUSER_DECLARED_METHODS=      (method->!method.isBridge() &&!method.isSynthetic());

可以看到,这里的逻辑就是过滤掉桥接方法和合成方法,可以简单理解为只筛选出我们通过写代码声明的方法。

过滤掉不需要处理的方法之后,再执行doWith方法,doWith方法的实现,其实就是在doWithMethods方法中传入的 Lambda 函数,其逻辑就是将没有标记@Pointcut注解的方法信息添加到事先创建的methods集合中。

此外,在doWithMethods方法的最后,还会递归处理当前类型的父类。

回到getAdvisorMethods方法中,当doWithMethods执行完之后,对获取到的methods进行排序,就得到了方法最重要返回的结果。

总结一下,getAdvisorMethods的原理就是,将指定类中所有声明的方法,过滤掉桥接方法、合成方法,以及标记了@Pointcut注解的方法(也就是定义切入点的方法),将剩下的方法进行排序得到结果。

创建增强方法对应的 Advisor

再回到getAdvisors方法中,接下来会遍历上一步得到的methods集合。

for (Methodmethod : getAdvisorMethods(aspectClass)) {
Advisoradvisor=getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor!=null) {
advisors.add(advisor);
   }
}

这里的逻辑非常简单,就是使用遍历到的方法信息method创建其对应的 Advisor,每一个增强方法都会对应一个 Advisor,这些 Advisor 会被添加到事先创建的advisors集合中。

这里的getAdvisor方法是一个关键的步骤,它的作用就是将增强方法封装成一个 Advisor 对象。这里面的内容比较到,限于篇幅,我打算单开一篇进行分析。本文中,我们先把getAdvisors方法后续的逻辑分析完。

后续的处理

以下是getAdvisors方法中后续的代码。

// If it's a per target aspect, emit the dummy instantiating aspect.if (!advisors.isEmpty() &&lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
AdvisorinstantiationAdvisor=newSyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}
// Find introduction fields.for (Fieldfield : aspectClass.getDeclaredFields()) {
Advisoradvisor=getDeclareParentsAdvisor(field);
if (advisor!=null) {
advisors.add(advisor);
   }
}
returnadvisors;

这里主要执行的逻辑是:

  • 如果这个切面配置是延迟初始化的, 那么在advisors集合的开头添加一个 SyntheticInstantiationAdvisor。
  • 根据切面配置类中标记了@DeclareParents注解的字段,生成相应的 DeclareParentsAdvisor 并添加到advisors集合中。
  • 将最终的advisors集合返回。

再返回最终结果之前的两步操作很少会用到,因此不做深入分析了。

总结

本文分析了在获取到基于注解的切面配置类之后,如何从中找到配置增强逻辑的方法,找到后,Spring 会为每一个增强逻辑创建一个 Advisor。下一篇,将深入分析 Advisor 创建的原理。

目录
相关文章
|
5天前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
28 0
|
7天前
|
前端开发 Java 应用服务中间件
Spring Boot自动配置详解
Spring Boot自动配置详解
|
7天前
|
Java Spring
深入理解Spring Boot中的Profile配置
深入理解Spring Boot中的Profile配置
|
XML Java 数据格式
[Spring实战系列](18)注解切面
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/50659876 使用注解来创建切面是AspectJ 5所引入的关键特性。
842 0
|
13天前
|
Java 开发者 Spring
深入理解Spring Boot中的自动配置原理
深入理解Spring Boot中的自动配置原理
|
14天前
|
前端开发 Java 微服务
Spring Boot与微前端架构的集成开发
Spring Boot与微前端架构的集成开发
|
20天前
|
Java
springboot自定义拦截器,校验token
springboot自定义拦截器,校验token
33 6
|
21天前
|
Java 关系型数据库 MySQL
Mybatis入门之在基于Springboot的框架下拿到MySQL中数据
Mybatis入门之在基于Springboot的框架下拿到MySQL中数据
18 4
|
21天前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
23 2
|
21天前
|
存储 运维 Java
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
Spring运维之boot项目开发关键之日志操作以及用文件记录日志
27 2