Spring AOP源码分析二

上一篇中,我们已经找到了AOP的源码入口,我们今天继续分析下面的代码,不过在此之前我们需要看下Spring中如何使用切面的,以便于我们理解我们的源码。代码如下:

package com.younger.web.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 日志切面
 */
@Component
@Aspect
public class LogAspect implements Ordered {


    /**
     * 定义切点
     */
    @Pointcut("execution(* com.younger.web.service.impl.system(..))")
    public void pointcut(){

    }

    private void pointCut1(){}

    /**
     * 前置通知
     */
    @Before("pointcut()")
    private static void before(){
        System.out.println("---前置通知---");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("---后置通知---");
    }


    @Override
    public int getOrder() {
        return 1;
    }
}

我们在项目中一般是通过@Aspect注解标记一个切面类的,在上面这个切面中实现了ordered接口,spring 会通过ordered的顺序来执行切面的,ordered值越小,优先级越高。那Spring是如何通过@Aspect注解找到切面类的呢?接下来我们就开始继续分析源码吧,上篇文章我们分析到了这个位置:
在这里插入图片描述
首先如果这个bean已经被处理过的话,那就直接返回,如果这个bean不需要被增强,就直接返回,如果这个bean被指定为不需要进行代理,也直接返回,就是一些常规校验。接着就会调用方法getAdvicesAndAdvisorsForBean(),我们到方法中看下:
在这里插入图片描述
这个方法主要就是调用findEligibleAdvisors()方法来获取合适的增强。我们到这个findEligibleAdvisors()方法看下:
在这里插入图片描述
在这个方法主要有两个主要的步骤:
1、调用findCandidateAdvisors方法获取所有的增强。
2、调用findAdvisorsThatCanApply方法找到与当前bean相匹配的增强。
我们先看下findCandidateAdvisors()方法是怎么获取所有的增强的,代码如下:
在这里插入图片描述
我们发现,在这个findCandidateAdvisors()方法中,首先调用了super.findCandidateAdvisors()方法来获取xml中配置的增强,也就是说我们不仅可以通过@Aspect注解方式配置的AOP,也可以使用xml方式配置AOP的。接着又调用了一个buildAspectJAdvisors()方法,为@Aspect注解标注的切面类构建增强,因为我们目前使用的是@Aspect注解的方式,所以super.findCandidateAdvisors()这行代码会返回一个空集合,我们现在来看下buildAspectJAdvisors()方法:

	public List<Advisor> buildAspectJAdvisors() {
		// @Aspect 注解标注切面类的beanName数组
		List<String> aspectNames = this.aspectBeanNames;

		// aspectNames为null表示之前没有构建过增强Advisors
		if (aspectNames == null) {
			synchronized (this) {
				aspectNames = this.aspectBeanNames;
				if (aspectNames == null) {
					List<Advisor> advisors = new ArrayList<>();
					aspectNames = new ArrayList<>();
					
					// 获取IOC容器中所有的bean
					String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
							this.beanFactory, Object.class, true, false);
					// 依次遍历没个bean
					for (String beanName : beanNames) {
						
						// 过滤不需要的bean
						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.
						
						// 获取bean的对应类型
						Class<?> beanType = this.beanFactory.getType(beanName, false);
						if (beanType == null) {
							continue;
						}
						// 判断这个bean是否为切面,就是看下这个类上有没有加 @Aspect 注解
						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);
								
								// 为切面类中的增强方法构建Advisors
								List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
								if (this.beanFactory.isSingleton(beanName)) {
									// 如果是单例,就将构建好的增强 classAdvisors 放入到缓存中
									this.advisorsCache.put(beanName, classAdvisors);
								}
								else {
									// 如果不是单例,那么就缓存 factory
									this.aspectFactoryCache.put(beanName, factory);
								}
								// 添加刚构建好的增强 Advisors
								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));
							}
						}
					}
					// 设置处理过切面类的beanName数组,方便下一次直接从缓存中获取
					this.aspectBeanNames = aspectNames;
					return advisors;
				}
			}
		}

		if (aspectNames.isEmpty()) {
			return Collections.emptyList();
		}
		List<Advisor> advisors = new ArrayList<>();
		for (String aspectName : aspectNames) {
			// 先尝试直接从缓存中获取增强 Advisors
			List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
			if (cachedAdvisors != null) {
				advisors.addAll(cachedAdvisors);
			}
			else {
				// 如果缓存advisorsCache 中没有获取到增强 advisors, 那么就是要 factory 快速构建新的增强 advisors
				MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
				advisors.addAll(this.advisorFactory.getAdvisors(factory));
			}
		}
		return advisors;
	}

在这里插入图片描述
我们可以看到,首先第一次调用这个buildAspectJAdvisors()方法时,这个aspectNames一定是个null,接着就会进入到这个if分支的处理中,首先从IOC容器中获取所有的bean,然后遍历依次处理每个bean。在处理bean的时候,主要有两个操作:
1、通过advisorFactory.isAspect()找到加了@Aspect注解的类;
2、通过this.advisorFactory.getAdvisors(factory)进一步找到切面类中的增强方法,并将增强方法构建为Advisor。
那我们就先来看下isAspect()方法是如何来找切面类的,isAspect()的方法代码如下:
在这里插入图片描述
可以看到,在isAspect方法中调用了hasAspectAnnotation() 方法,接着又调用了AnnotationUtils.findAnnotation()。而在方法中传入了Aspect.class参数,我们看一下这个Aspect:
在这里插入图片描述
这个类就是我们在切面类上加的那个注解@Aspect。我们看下这个AnnotationUtils.findAnnotation方法:
在这里插入图片描述
其实这个AnnotationUtils.findAnnotation()方法,就是专门用来在指定类上找特定注解的。
我们继续看下面的流程:
在这里插入图片描述
在分析这个构建 Advisors源码之前,我需要知道我们平时在使用AOP时,增强逻辑都会放在切面中一个一个的方法中,那么在为目标方法添加增强逻辑时,当然要拿到切面类中声明的这些方法,这样才能去执行对应的增强逻辑。所以这里不光要找到切面类,还要获取到切面类中方法,目前为止我们已经找到了切面类,那么下一步当然就是要获取到切面类中的方法,来看下Spring是怎么获取到切面类中方法的:

@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<>();
		// 获取切面类中声明的方法
		for (Method method : getAdvisorMethods(aspectClass)) {
			
			// 为增强方法构建增强实例 Advisors, 这个 Advisors 就是一个 InstantiationModelAwarePointcutAdvisorImpl
			Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, 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;
	}

在这里插入图片描述
可以看到,这里首先通过getAdvisorMethods(aspectClass)方法获取切面类中声明的方法,然后调用Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName)依次为每一个方法生成对应的增强Advisor。
首先我们要看的,就是获取切面类中方法的逻辑了,这个时候我们可以点进去getAdvisorMethods(aspectClass)方法来看下,此时代码如下:
在这里插入图片描述
我们可以看到,这里主要调用了ReflectionUtils.doWithMethods()来获取切面类中的方法。并且我们可以看到调用doWithMethods()方法时需要传入一些参数,那这些参数代表什么意思呢?我们来看下这个doWithMethods()方法的定义:
在这里插入图片描述
此时我们看到这个doWithMethods()方法定义了三个参数,分别是Class<?> clazz、MethodCallback mc和MethodFilter mf,第一个参数就是一个class对象,第二个和第三个参数通过参数名我们可以判断出,第二个参数MethodCallback是一个回调方法,而第三个参数是一个过滤条件。我们分别来看一下MethodCallback和MethodFilter分别是怎么定义的吧,如下图:
在这里插入图片描述
可以看到MethodCallback和MethodFilter的定义中,它们都加了@FunctionalInterface注解,所以MethodCallback和MethodFilter都是函数式接口,我们可以使用Lambda表达式调用它们。现在我们知道了doWithMethods()方法的第二个参数和第三个参数都是函数式接口,那我们调用doWithMethods()方法时,这三个参数都是怎么指定的呢?我们再回头来看下调用doWithMethods()方法的地方吧,如下图:
在这里插入图片描述
首先第一个参数aspectClass,就是指定切面类,第二个参数回调方法的本质,其实就是实现了MethodCallback接口的doWith()方法了,而第三个参数过滤条件的本质,则是实现了MethodFilter接口的matches()方法。看到这三个参数,我们就大致明白了doWithMethods()方法要做的事情,就是过滤出指定类aspectClass中的方法,过滤条件就是这个ReflectionUtils.USER_DECLARED_METHODS,最后通过回调方法将满足条件的method放到一个List集合中。我们知道了doWithMethods()方法要做的事情只之后,那现在就来看下它是怎么实现的吧,我们看这里:
在这里插入图片描述
首先就是先调用getDeclaredMethods()方法获取切面类中声明的方法methods,接着遍历处理这些method。
先调用mf.matches(method)过滤出要处理的方法,最后将过滤出来的方法,通过回调逻辑统一放到一个List集合中,我们这里来看一下方法过滤是怎么做的。我们点进去mf.matches(method)来看下,这个mf大家还记得吗?它其实就是一个函数式接口的实现类,而我们传进来的是ReflectionUtils.USER_DECLARED_METHODS,所以我们现在来看下这个ReflectionUtils.USER_DECLARED_METHODS到底是个啥,如下图:
在这里插入图片描述
可以看到,这里直接调用了Method对象的isBridge()方法和isSynthetic()方法来做的过滤,而这个isBridge()方法是用来判断当前方法是否为桥接方法,而isSynthetic()方法是用来判断当前方法是否为合成方法。简单来说,桥接方法和合成方法是编译器由于内部需要,编译器自己创建出来的方法,而不是我们自己创建的方法。!method.isBridge() && !method.isSynthetic() 这行代码在当前方法为非桥接方法且非合成方法时会返回true,说白了就是过滤出我们自己创建的方法,排除掉那些编译器内部创建的方法。那么当mf.matches(method)这行代码执行完毕,究竟能从切面类中过滤出来哪些方法呢?我们这里以日志切面类为例,来看下哪些方法是非桥接且非合成方法吧,首先我们先回顾一下日志切面类LogAspect ,代码如下:
在这里插入图片描述
在切面类LogAspect中的pointcut()、 before()、 afterReturning()、getOrder()这四个方法既不是桥接方法,也不是合成的方法,这些方法都是我们自己创建的方法,所以这四个方法执行到mf.matches()方法时,mf.matches()方法返回的都是true。mf.matches()方法执行完之后,就开始回调执行方法了,如下图:
在这里插入图片描述
这里其实直接调用了入参传进来mc的doWith()方法,这个mc实现类是我们自己传递进来的,此时就会回调到下面的逻辑,我们看这里:
在这里插入图片描述
其实就是将过滤出来的方法放到一个List集合中,但是我们发现在此之前,还有一个if条件,就是AnnotationUtils.getAnnotation(method, Pointcut.class) == null这行代码,只有当这个条件成立时,才会将方法放入到List集合中。并且我们还发现在调用getAnnotation()方法时,除了将method传进去之外,还传进去了一个Pointcut.class,这个Pointcut.class又是个什么?我们点进去看下Pointcut.class,代码如下:
在这里插入图片描述
这个就是我们的@Pointcut注解,我看下:
在这里插入图片描述
在切面类LogAspect的pointcut() 方法上加了一个@Pointcut注解,用来定义切点表达式使用的。那这AnnotationUtils.getAnnotation(method, Pointcut.class) 方法到底是干嘛的?我们可以先点进去看下AnnotationUtils.getAnnotation()方法的定义,如下图:
在这里插入图片描述
我们可以看到这个方法有两个入参,第一个参数是方法对象本身,第二个参数是注解类型,通过上面的注释,我们可以判断出这个getAnnotation()方法,其实就是用来在指定的方法method上获取特定注解的。那也就是说AnnotationUtils.getAnnotation(method, Pointcut.class) 方法是用来获取方法上的@Pointcut
注解的,那么AnnotationUtils.getAnnotation(method, Pointcut.class) == null又是什么意思呢?首先这个AnnotationUtils.getAnnotation(method, Pointcut.class) 方法什么时候会返回空?当然是这个方法上没有加@Pointcut注解的时候才会返回null,因为此时在这个方法上找不到@Pointcut注解,那当然要返回null,那么此时AnnotationUtils.getAnnotation(method, Pointcut.class) == null的结果为true,那么就会将这个方法添加到到List集合中,其实就是执行下边这行代码:
在这里插入图片描述
那如果此时当前方法加了@Pointcut注解呢?比如切面类LogAspect的pointcut() 方法上就加了一个@Pointcut注解。那此时AnnotationUtils.getAnnotation(method, Pointcut.class) == null的结果就为false,此时就不会将这个pointcut() 方法添加到List结合中。其实说白了这个条件就是用来排除掉@Pointcut注解的pointcut() 方法的。以切面类LogAspect为例,在执行完mf.matches()时,由于pointcut()、before()、afterReturning()、getOrder()这四个方法既不是桥接方法,也不是合成方法,所以这四个方法都可以正常通过。而执行到AnnotationUtils.getAnnotation(method, Pointcut.class) == null这个条件时,会将pointcut()方法给排除掉,因为pointcut()方法上加了@Pointcut注解,所以此时就只剩下before()、afterReturning()、getOrder()这三个方法了,最后就会将这三个方法放入到一个List集合中,然后作为getAdvisorMethods()方法的结果返回。

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

youngerone123

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

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

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

打赏作者

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

抵扣说明:

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

余额充值