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 创建的原理。

目录
相关文章
|
6天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
20小时前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
12 6
|
1天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
10 3
|
2天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
4天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
13 1
|
5天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
18 2
|
6天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
6天前
|
Java Spring
Spring的@Retryable实现方法重试
`@Retryable`注解用于配置异常重试,参数包括:指定异常类型`value`,额外包含异常`include`,排除异常`exclude`,最大尝试次数`maxAttempts`和回退策略`backoff`。可选地,可以用`retryExceptions`列表替换`value`。当重试失败,可使用`@Recover`注解定义恢复逻辑。
|
6天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
16 2
|
10天前
|
Java 微服务 Spring
Spring Boot中获取配置参数的几种方法
Spring Boot中获取配置参数的几种方法
21 2