Spring AOP源码分析三

上篇文章我们已经获取了切面类中自己声明的方法,我们接着之前的代码进行分析,我们继续分析Spring是如何获取增强方法上的AspectJ注解信息的并且如何对增强方法进行匹配的,我们回到之前我们分析的代码:
在这里插入图片描述
现在这个getAdvisorMethods() 方法会返回before()、afterReturning()、getOrder()这三个方法。接着就是遍历依次处理这个三个方法,我们来看一下:
在这里插入图片描述
这个行代码就是调用了getAdvisor() 方法,从切面类中获取的method传递进去。我们到这个getAdvisor() 方法中看下:
在这里插入图片描述
首先就是调用getPointcut()方法来获取method上的切点信息,然后用获取到的切点信息构建 InstantiationModelAwarePointcutAdvisorImpl类的实例,最后将这个实例返回。我们就先来看下getPointcut()方法是怎么获取切点信息,我们看下面的代码:
在这里插入图片描述
我们发现这里直接调用了一个findAspectJAnnotationOnMethod()方法来获取AspectJ注解,然后用获取到的AspectJ注解构建了一个切点对象ajexp,这个切点ajexp其实一个AspectJExpressionPointcut类的实例。我们来看下findAspectJAnnotationOnMethod()方法是怎么来获取AspectJ注解,我们点开findAspectJAnnotationOnMethod()方法:
在这里插入图片描述
我们发现,这里遍历了一个注解数组ASPECTJ_ANNOTATION_CLASSES,然后调用findAnnotation()方法依次在指定的method上查找ASPECTJ_ANNOTATION_CLASSES数组中的注解,那ASPECTJ_ANNOTATION_CLASSES数组到底有哪些注解呢?这个ASPECTJ_ANNOTATION_CLASSES数组的定义如下:
在这里插入图片描述
这里会依次在指定method上查找AspectJ的六种注解,分别是@Pointcut、@Around、@Before、@After、@AfterReturning、@AfterThrowing,具体查找的话就是这行代码AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class) clazz),就是调用工具方法findAnnotation()来完成AspectJ注解的扫描,最后返回扫描到的AspectJ注解。刚才getAdvisorMethods()返回的before()、afterReturning()、getOrder()这三个方法,在处理getOrder()这个method时,findAspectJAnnotationOnMethod()方法就会返回null,因为getOrder()方法上没有加AspectJ的六种注解之一。
我们接着往下看:
在这里插入图片描述
在getPointcut()方法中,接收到findAspectJAnnotationOnMethod()方法扫描到的注解aspectJAnnotation后,接着会构建一个切点对象ajexp,也就是AspectJExpressionPointcut类的一个实例,并且将aspectJAnnotation的切点表达式设置到切点对象ajexp中,最后将切点对象ajexp返回。当getPointcut()方法将切点对象expressionPointcut返回后,在getAdvisor()方法中,直接通过构造方法的方式创建了一个InstantiationModelAwarePointcutAdvisorImpl类的实例,代码如下:
在这里插入图片描述
我们可以看到,这里直接通过构造方法的方式,将增强方法 candidateAdviceMethod 和 切点 expressionPointcut 等关键信息注入到了InstantiationModelAwarePointcutAdvisorImpl类的实例中。其中InstantiationModelAwarePointcutAdvisorImpl类的构造方法的代码如下:
在这里插入图片描述
我们知道最后构造出来的这个Advisor,其实就是InstantiationModelAwarePointcutAdvisorImpl类的一个实例,这个Advisor中包含了增强方法 candidateAdviceMethod 和 切点表达式 expressionPointcut 等关键信息。方法getAdvisorMethods()会返回before()、afterReturning()、getOrder()这三个方法,接着开始调用getAdvisor()为这些方法创建对应的Advisor。而在创建Advisor的过程中,其实这个getOrder()方法是会被过滤掉,因为getOrder()上没有添加AspectJ注解,所以最终能正常创建advisor的只有before()和afterReturning()这两个增强方法,最后,这两个增强方法会被放入到集合advisors中作为结果返回,如下图:
在这里插入图片描述
到这里为止,List classAdvisors = this.advisorFactory.getAdvisors(factory)这行代码就执行完毕了,如下图:
在这里插入图片描述
此时我们以切面类LogAspect为例,经过List classAdvisors = this.advisorFactory.getAdvisors(factory)这行代码处理之后,返回结果classAdvisors中就只剩下before()和afterReturning()这两个增强方法对应的Advisor了。接着Spring会将构建好的classAdvisors放入到缓存中,而缓存对应的key就是beanName,如下图:
在这里插入图片描述
我们可以看到,其实放入缓存的时候做了不同的处理,如果是单例bean的话,那么就直接将构建好的增强classAdvisors放入缓存中,这样下一次就可以直接从缓存中获取了,如果不是单例bean的话,那么就直接将factory放入缓存中,这样方便下一次直接使用factory快速构建Advisors。最后一旦将增强classAdvisors或factory放入缓存后,那么下次调用这个buildAspectJAdvisors()方法时,就直接可以从缓存中快速获取了,如下图:
在这里插入图片描述
接着buildAspectJAdvisors()方法构建的advisor就会放入到advisors集合中,如下图:
在这里插入图片描述
经过这个findCandidateAdvisors()方法的处理,那么IOC容器中所有的增强都会被放入到advisors集合中,最后作为结果返回。到目前为止这行代码List candidateAdvisors = findCandidateAdvisors()已经执行完毕了,如下图:
在这里插入图片描述
此时返回的这个candidateAdvisors中就包含了IOC容器中所有切面类中的所有增强Advisor,我们总不能让这些增强都适用于当前bean,这个肯定是不对的,有可能我们这个bean完全都不满足某一个切面类的切点表达式。就是从所有切面类的所有增强Advisor中,找到与当前bean相匹配的增强Advisors,也就是执行List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName)这行代码了。这个findAdvisorsThatCanApply()方法会使用增强Advisor中的切点表达式与当前bean中的方法做匹配,从而找到与当前bean匹配的增强Advisor,我们接着往下分析:
在这里插入图片描述
这里调用了findAdvisorsThatCanApply()方法,然后将candidateAdvisors和bean作为入参给传了进去。从这个方法名字和出参eligibleAdvisors的名字来看,这个findAdvisorsThatCanApply()方法的作用,就是用来完成bean和candidateAdvisors之间的匹配的,最后会将bean匹配到的增强advisors作为出参进行返回。我们点进去这个findAdvisorsThatCanApply()方法来看一下,代码如下图:
在这里插入图片描述
这里直接调用了AopUtils.findAdvisorsThatCanApply()方法进行的匹配,然后将candidateAdvisors和当前要匹配的beanClass作为入参给传了进去。我们进到AopUtils.findAdvisorsThatCanApply()方法来看一下:

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		// 对IntroductionAdvisor引介增强的处理,控制粒度为类级别,一般不常用
		// 目前我们增强的类型为PointcutAdvisor,所以不会走这里
		for (Advisor candidate : candidateAdvisors) {
			/**
			 * 一般我们使用@Pointcut注解方式定义切点的话,Advisor会通过 InstantiationModelAwarePointcutAdvisorImpl 来进行构建
			 *  并且 InstantiationModelAwarePointcutAdvisorImpl 并且没有实现 IntroductionAdvisor 接口,实现的是PointcutAdvisor 接口
			 *  所以使用 @Pointcut注解定义切点这种方式的话,是不会走这里的逻辑的
			 *  
			 */
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			// 这个已经处理了
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				continue;
			}
			// 处理普通增强,找到与当前bean向匹配的增强
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}

在这里插入图片描述
我们可以看到,这里有两块处理逻辑,也就是红框圈出来的2块代码,我们发现这2块逻辑都会往最终结果eligibleAdvisors中添加元素。
其中第一块逻辑,也就是第一个for循环中,是当 candidate instanceof IntroductionAdvisor 为true时,就会调用canApply(candidate, clazz)方法进行匹配。而第二块逻辑,也就是第二个for循环中,是当 candidate instanceof IntroductionAdvisor 为false时,会调用canApply(candidate, clazz, hasIntroductions)方法进行匹配。那这个IntroductionAdvisor到底是什么东西呢?此时我们可以点进来看下IntroductionAdvisor的定义,代码如下:

/**
 * Superinterface for advisors that perform one or more AOP <b>introductions</b>.
 *
 * <p>This interface cannot be implemented directly; subinterfaces must
 * provide the advice type implementing the introduction.
 *
 * <p>Introduction is the implementation of additional interfaces
 * (not implemented by a target) via AOP advice.
 *
 * @author Rod Johnson
 * @since 04.04.2003
 * @see IntroductionInterceptor
 */
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

	/**
	 * Return the filter determining which target classes this introduction
	 * should apply to.
	 * <p>This represents the class part of a pointcut. Note that method
	 * matching doesn't make sense to introductions.
	 * @return the class filter
	 */
	ClassFilter getClassFilter();

	/**
	 * Can the advised interfaces be implemented by the introduction advice?
	 * Invoked before adding an IntroductionAdvisor.
	 * @throws IllegalArgumentException if the advised interfaces can't be
	 * implemented by the introduction advice
	 */
	void validateInterfaces() throws IllegalArgumentException;

}

我们发现这个IntroductionAdvisor其实就是一个接口,而且它还继承了Advisor接口,其实这个IntroductionAdvisor代表了一种增强,我们一般称为“引介增强”,这种引介增强控制的是类级别,控制粒度是比较粗的,所以一般我们不常用,其实candidate instanceof IntroductionAdvisor这行代码,就是用来判断candidate是不是引介增强的。这里的candidate,也就是candidateAdvisors数组中的Advisor到底是不是引介增强呢?说白了就是看有没有实现IntroductionAdvisor接口?我们知道这个Advisor其实是InstantiationModelAwarePointcutAdvisorImpl类的一个实例,因为构建这个Advisor的时候是直接通过InstantiationModelAwarePointcutAdvisorImpl类的构造方法来完成的。我们直接来看下InstantiationModelAwarePointcutAdvisorImpl类的继承关系。nstantiationModelAwarePointcutAdvisorImpl类的继承关系如下图:
在这里插入图片描述我们可以看到,PointcutAdvisor接口(普通增强)和IntroductionAdvisor接口(引介增强)都继承了Advisor接口,而InstantiationModelAwarePointcutAdvisorImpl类实现的是PointcutAdvisor接口,而不是IntroductionAdvisor接口,也就是说当前这个candidate并不是引介增强,而是一个普通增强。

所以此时candidate instanceof IntroductionAdvisor这行代码会返回false,接着就会执行普通增强的匹配逻辑,也就是会执行这行代码,如下图:
在这里插入图片描述
我们在定义切面的时候是指定了切点表达式的,利用切点表达式我们可以匹配到方法级别,由于普通增强控制的粒度更细,所以我们一般会使用这种PointcutAdvisor类型的增强,也就是普通增强。我们到方法canApply(candidate, clazz, hasIntroductions)中看下:
在这里插入图片描述
可以看到这里有引介增强和普通增强的处理,我们这里的advisor就是InstantiationModelAwarePointcutAdvisorImpl类的一个实例,也就是PointcutAdvisor接口的实现类,因此这里就会执行canApply(pca.getPointcut(), targetClass, hasIntroductions)这行代码来进行方法级别的匹配。此时我们点进去canApply(pca.getPointcut(), targetClass, hasIntroductions)方法,会看到下面的代码:

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		Assert.notNull(pc, "Pointcut must not be null");
		// 先在类级别进行匹配,如果不匹配,那么直接返回false
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}
		
		
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}

		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
			// 将目标类,也就是当前要匹配的bean,放入到classes集合中
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		// 将目标类实现的接口也放入到classes集合中
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

		// 遍历处理目标类和目标类的接口
		for (Class<?> clazz : classes) {
			// 获取目标类中的方法
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			// 遍历处理目标类中的方法
			for (Method method : methods) {
				// 目标类中只要有一个方法被匹配到,那么就直接返回true,就是需要为这个类设置代理
				if (introductionAwareMethodMatcher != null ?
						// 目标类方法与切点表达式进行匹配
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

在这里插入图片描述
在这里插入图片描述
可以看到,这里分别在类级别和方法级别进行匹配,首先会调用pc.getClassFilter().matches(targetClass)进行类级别的匹配,就是使用切点表达式和目标类进行匹配,如果在类级别都不满足切点表达式的话,那么就没必要进一步去匹配方法了。只有当类级别满足切点表达式之后,才会进行方法级别的匹配,此时就会获取目标类中的方法,然后依次判断每个方法是否与切点表达式正常匹配,只要目标类中有一个方法可以和切点表达式匹配上,那么就直接返回true,此时就需要为这个目标类设置代理。这个目标类方法和切点表达式是怎么进行匹配的,我们点进去introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)方法来看一下,代码如下:
在这里插入图片描述
我们可以看到这里直接调用getTargetShadowMatch(method, targetClass)方法获取了匹配结果shadowMatch,如果shadowMatch.alwaysMatches()方法的结果为true的话,那么就说明这个类是需要创建代理的。我们到getTargetShadowMatch(method, targetClass)方法中:
在这里插入图片描述这里调用了另外一个方法getShadowMatch(targetMethod, method)。那我们继续跟进到getShadowMatch(targetMethod, method)方法中:
在这里插入图片描述
刚进来这个方法时,会看一下缓存shadowMatch是不是空,第一次进来的话肯定是没有的,所以就会进来这个if分支的代码,那么此时就会执行非常核心的一块代码,就是shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch)这行代码。我们来看下这个obtainPointcutExpression()方法是干嘛的,代码如下:
在这里插入图片描述
这个obtainPointcutExpression()方法中,进行了AspectJ切点表达式的解析,比如我们日志切面类中的切点表达式execution(execution(* com.younger.web.service.impl.system(…))),最后解析完切点表达式之后,会将切点表达式构建为一个PointcutExpressionImpl类的实例,也就是说obtainPointcutExpression()方法返回的其实就是一个PointcutExpressionImpl类的实例。我们执行到了shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch)这行代码,现在obtainPointcutExpression()方法返回了一个PointcutExpressionImpl类的实例,那么接下来就会调用PointcutExpressionImpl类的matchesMethodExecution(methodToMatch)来完成切点表达式和目标类的匹配。

我们通过一张流程图总结下今天内容:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

youngerone123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值