Spring 源码阅读 54:切入点表达式与目标方法的匹配

简介: 本文介绍了 Spring 通过findEligibleAdvisors查找与目标类匹配的 Advisor 的原理。

基于 Spring Framework v5.2.6.RELEASE

概述

前面几篇文章分析了用于创建 AOP 代理的后处理器,如何从容器中找到所有的增强逻辑对应的 Advisor 对象。查找的过程由后处理器的findCandidateAdvisors方法执行,得到结果后,返回到findEligibleAdvisors方法中。

接下来,后处理器将会从这些 Advisor 中,筛选出与当前处理的 Bean 实例匹配的 Advisor。本文将继续分析后面的代码。

查找匹配的 Advisor

先查看一下findEligibleAdvisors方法的代码。

// org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisorsprotectedList<Advisor>findEligibleAdvisors(Class<?>beanClass, StringbeanName) {
List<Advisor>candidateAdvisors=findCandidateAdvisors();
List<Advisor>eligibleAdvisors=findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors=sortAdvisors(eligibleAdvisors);
   }
returneligibleAdvisors;
}

之前的几篇文章,分析的都是方法中的第一行代码,也就是findCandidateAdvisors方法的原理,在这个方法执行完,获得到 Advisor 列表candidateAdvisors之后,会在下一步通过findAdvisorsThatCanApply方法,从candidateAdvisors中找出与当前处理的 Bean 实例匹配的 Advisor 集合。

我们进入findAdvisorsThatCanApply方法。

// org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApplyprotectedList<Advisor>findAdvisorsThatCanApply(
List<Advisor>candidateAdvisors, Class<?>beanClass, StringbeanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
returnAopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
   }
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
   }
}

其中核心的逻辑只有一行,就是调用 AopUtils 的findAdvisorsThatCanApply方法,我们进入这个方法。

image.png

这个方法中的逻辑,我将分成几部分来分析。

if (candidateAdvisors.isEmpty()) {
returncandidateAdvisors;
}
List<Advisor>eligibleAdvisors=newArrayList<>();

首先判断参数传入的 Advisor 集合是不是空的,如果是空的,那就不需要筛选了,直接返回空集合即可。如果不是空的,那么初始化一个 Advisor 集合eligibleAdvisors,接下来的逻辑就是向这个集合中添加内容,最终这个集合会作为方法的结果返回。

接下来进入第一个for循环语句。

for (Advisorcandidate : candidateAdvisors) {
if (candidateinstanceofIntroductionAdvisor&&canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
   }
}

此处会遍历candidateAdvisors中的所有 Advisor 进行遍历,并将符合if语句判断条件的添加到eligibleAdvisors中。这里需要当前遍历到的 Advisor 同时满足两个判断条件:

  1. 它实现了 IntroductionAdvisor 接口;
  2. canApply方法返回true

这里先了解一下 IntroductionAdvisor,先看 Advisor 相关类型的关系。

image.png

如果你看过我之前的文章,这里可以看到一些熟悉的类型。

对于通过 XML 配置的切面信息,每一个增强方法会被创建成一个 AspectJPointcutAdvisor 类型的 Advisor 对象,而对于注解配置的切面信息,每一个增强方法会被创建成一个 InstantiationModelAwarePointcutAdvisorImpl 类型的 Advisor 对象。以上二者都实现了 PointcutAdvisor 接口。

而 IntroductionAdvisor 接口与 PointcutAdvisor 接口都是 Advisor 的子接口,它的实现类之一 DeclareParentsAdvisor 我们之间在分析代码时也见过,如果一个切面配置类的某个字段被标记了@DeclareParents注解,那么它就会有一个对应的 DeclareParentsAdvisor 类型的 Advisor 对象。

由此可知,平时我们配置的增强方法对应的都是 PointcutAdvisor 的实现,那 IntroductionAdvisor 是什么呢?PointcutAdvisor 的增强目标可以精确到一个方法,而 IntroductionAdvisor 增强是针对类的,因此我们很少用到。

然后我们再看一下canApply方法。

// org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class<?>)publicstaticbooleancanApply(Advisoradvisor, Class<?>targetClass) {
returncanApply(advisor, targetClass, false);
}
// org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)publicstaticbooleancanApply(Advisoradvisor, Class<?>targetClass, booleanhasIntroductions) {
if (advisorinstanceofIntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
   }
elseif (advisorinstanceofPointcutAdvisor) {
PointcutAdvisorpca= (PointcutAdvisor) advisor;
returncanApply(pca.getPointcut(), targetClass, hasIntroductions);
   }
else {
// It doesn't have a pointcut so we assume it applies.returntrue;
   }
}

只看 IntroductionAdvisor 部分的逻辑,其实就是判断当前的 IntroductionAdvisor 是不是匹配目标类型。

回到findAdvisorsThatCanApply方法中,会先将candidateAdvisors中匹配当前类型的 IntroductionAdvisor先添加到eligibleAdvisors中。

再接着看后面的代码。

booleanhasIntroductions=!eligibleAdvisors.isEmpty();
for (Advisorcandidate : candidateAdvisors) {
if (candidateinstanceofIntroductionAdvisor) {
// already processedcontinue;
   }
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
   }
}

首先,声明一个hasIntroductions变量,表示上一步中是否找到了匹配的 IntroductionAdvisor。然后,再次遍历candidateAdvisors,跳过 IntroductionAdvisor,也就是说,这个for循环之处理 PointcutAdvisor,通过canApply方法的判断,再将符合条件的 PointcutAdvisor 添加到eligibleAdvisors中。

这里我们再一次进入canApply方法查看 PointcutAdvisor 的部分。

// org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)publicstaticbooleancanApply(Advisoradvisor, Class<?>targetClass, booleanhasIntroductions) {
if (advisorinstanceofIntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
   }
elseif (advisorinstanceofPointcutAdvisor) {
PointcutAdvisorpca= (PointcutAdvisor) advisor;
returncanApply(pca.getPointcut(), targetClass, hasIntroductions);
   }
else {
// It doesn't have a pointcut so we assume it applies.returntrue;
   }
}

判断的逻辑交给了另外一个canApply方法,进入这个重载方法。

image.png

我们分步解析。

if (!pc.getClassFilter().matches(targetClass)) {
returnfalse;
}

首先,先看类型是否能够匹配,如果类型都不能匹配,那就没必要再匹配方法了。

MethodMatchermethodMatcher=pc.getMethodMatcher();
if (methodMatcher==MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...returntrue;
}

然后再看切入点是否是匹配类中的所有方法,如果是的话,则直接返回true

IntroductionAwareMethodMatcherintroductionAwareMethodMatcher=null;
if (methodMatcherinstanceofIntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher= (IntroductionAwareMethodMatcher) methodMatcher;
}

接下来,会判断methodMatcher是否是 IntroductionAwareMethodMatcher 接口的实现,如果是,则强转类型,赋值给introductionAwareMethodMatcher变量。这里我们来分析一下methodMatcher的类型。

methodMatcher的来源是上述代码中的pc.getMethodMatcher(),也就是从切入点对象中获取到的,如果你看过之前的源码分析文章,可以知道,无论是通过 XML 配置还是注解配置,切入点都会被解析成一个 AspectJExpressionPointcut 类型的 Bean,我们找到 AspectJExpressionPointcut 中的getMethodMatcher方法。

@OverridepublicMethodMatchergetMethodMatcher() {
obtainPointcutExpression();
returnthis;
}

可以发现,返回的是它本身,而它又是实现了 IntroductionAwareMethodMatcher 接口的。因此,上述的if判断结果是true

回到canApply方法接着往下看。

Set<Class<?>>classes=newLinkedHashSet<>();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

将当前要匹配的目标类及其实现的接口类型添加到一个事先初始化好的classes集合当中。

for (Class<?>clazz : classes) {
Method[] methods=ReflectionUtils.getAllDeclaredMethods(clazz);
for (Methodmethod : methods) {
if (introductionAwareMethodMatcher!=null?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
returntrue;
      }
   }
}
returnfalse;

遍历classes中的每一个类型中声明的每一个方法,通过切入点的方法匹配器的matches进行匹配,只要有一个方法与切入点匹配,则代表当前处理的目标类是需要创建 AOP 代理的,就返回true,如果一个方法都没匹配到,则在方法的末尾返回false

至此,介绍完以上的流程,findEligibleAdvisors方法的逻辑就介绍完了。

总结

本文介绍了 Spring 通过findEligibleAdvisors查找与目标类匹配的 Advisor 的原理,其中包含类级别的匹配和方法的匹配,只要 Bean 实例的类中有一个方法被匹配到,那么,Bean 就需要被代理。

目录
相关文章
|
3天前
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
|
12天前
|
JavaScript Java 数据安全/隐私保护
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
20 0
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
|
16天前
|
Java 应用服务中间件 Maven
ContextLoaderListener在Spring应用中的作用与配置方法
ContextLoaderListener在Spring应用中的作用与配置方法
|
17天前
|
存储 NoSQL Java
教程:Spring Boot与RocksDB本地存储的整合方法
教程:Spring Boot与RocksDB本地存储的整合方法
|
12天前
|
JavaScript Java 关系型数据库
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(1)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
15 0
|
12天前
|
Java API 微服务
Spring Boot中的跨服务调用方法
Spring Boot中的跨服务调用方法
|
12天前
|
负载均衡 监控 Java
Spring Boot与微服务治理框架的集成方法
Spring Boot与微服务治理框架的集成方法
|
12天前
|
Java API 数据中心
Spring Cloud中的服务注册与发现实现方法
Spring Cloud中的服务注册与发现实现方法
|
5天前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
28 0
|
13天前
|
Java 开发者 Spring
深入理解Spring Boot中的自动配置原理
深入理解Spring Boot中的自动配置原理