Spring5.X AOP 通知的递归调用链实现源码分析

写在前面

  • 具备Spring IOC以及AOP源码分析基础 <单击一键,原理尽显>
  • Advisor
      它包含AOP 通知(在连接点采取的操作)和确定建议适用性的过滤器(例如切入点),简单的说的来说可以理解成拦截器或者切面。
  • @DeclareParents
       用来标注对象属性,可以为对象添加一个新方法。
  • InterceptorAndDynamicMethodMatcher
       动态拦截器,根据运行时参数来决定拦截器是否生效,有动态拦截器当然也有静态拦截器,静态拦截器一般通过包名,类名,方法名 ,参数来确定静态拦截器是否对代理方法生效。
  • 本文调试代码获取
  • 阅读本文你将收获
    1. AOP切面配置如何被加载的?
    2. PartialOrder究竟对Advice(MethodIntercept)做出了何种排序?
    3. AOP如何通过递归实现的调用链设计模式?
    4. 事务为何会失效?

前情回顾

  AOP的一切要从 @EnableAspectJAutoProxy说起。

  它通过 @Import注解AspectJAutoProxyRegistrar类,这个类是ImportBeanDefinitionRegistrar实现类。

  通过这种手动注入方式,最终将AnnotationAwareAspectJAutoProxyCreator注入到IOC容器中。

  查看AnnotationAwareAspectJAutoProxyCreator的类图,可以发现它是BeanPostProcessor的实现,BeanPostProcessor允许对Bean进行增强操作。

  增强操作分为两种:前置(postProcessBeforeInitialization)、后置(postProcessAfterInitialization)增强,最终我们在AnnotationAwareAspectJAutoProxyCreator的其中一个超类中找到了这两个增强方法的实现,那就是AbstractAutoProxyCreator

	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean;
	}

	/**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

  最终,我们发现AbstractAutoProxyCreator在前置增强中并未做任何处理,而我们的故事也就是从后置增强中开始……

  咳咳~

  好啦,故事我们讲完了,现在开始源码分析。

源码分析

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		// 省略部分代码……
		// 1、获取所有Advisors(可以理解成拦截器或者切面),官方定义https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/Advisor.html
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 2、创建代理
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

  我将整个过程分两步:

            1、切入点的解析与Advice初排序
            2、代理中的递归调用链逻辑分析

  它们分别隶属getAdvicesAndAdvisorsForBean()分支以及createProxy()分支,那么我们先来看第一部分的源码分析。

切入点的解析与Advice初排序

切入点加载的调用链源码解析

  IDEA Ctrl+Alt+B 可以发现,getAdvicesAndAdvisorsForBean有两种实现,当方法是具有多个实现的抽象方法时,可以选择调试的方式确定最终的调用方向。

  最终,我们可以确定代码走向AbstractAdvisorAutoProxyCreator的实现。

	protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}
	
	// 寻找合格的顾问
——> protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	// 查询所有候选Advisors
	// Ctrl+Alt+B 进入AnnotationAwareAspectJAutoProxyCreator的findCandidateAdvisors()实现
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
	

——> protected List<Advisor> findCandidateAdvisors() {
		// 执行父类中的findCandidateAdvisors()方法,也就是我们上一段代码所在类。
		List<Advisor> advisors = super.findCandidateAdvisors();
		// 查询添加Bean工厂中所有的AspectJ切面Advisor。
		if (this.aspectJAdvisorsBuilder != null) {
		// 查询添加Bean工厂中所有的AspectJ切面Advisor,被@AspectJ标注的类将被IOC做特殊标记
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		}
		return advisors;
	}

  到这里我们可以初略的将Advisor分为两类,过滤器以及切面,接下来我们重点关注一下切面(@AspectJ标注的类)如何被解析的?以及如何进行的排序?

——> public List<Advisor> buildAspectJAdvisors() {
		List<String> aspectNames = this.aspectBeanNames;

		if (aspectNames == null) {
			synchronized (this) {
				aspectNames = this.aspectBeanNames;
				if (aspectNames == null) {
					List<Advisor> advisors = new LinkedList<>();
					aspectNames = new LinkedList<>();
					String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
							this.beanFactory, Object.class, true, false);
					for (String beanName : beanNames) {
						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.
						Class<?> beanType = this.beanFactory.getType(beanName);
						if (beanType == null) {
							continue;
						}
						// 1、过滤被@AspectJ标注的Bean
						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);
								// 2、获取当前切面下的所有Advisor切入点
								List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
								if (this.beanFactory.isSingleton(beanName)) {
									this.advisorsCache.put(beanName, classAdvisors);
								}
								else {
									this.aspectFactoryCache.put(beanName, factory);
								}
								advisors.addAll(classAdvisors);
							}
							else {
								// 省略部分代码……
							}
						}
					}
					this.aspectBeanNames = aspectNames;
					return advisors;
				}
			}
		}

		// 省略部分尾部代码……
		return advisors;
	}

  Spring 如何获取切入点信息?初排序Order如何设置?

——>public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
		Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
		String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
		// 验证当前实例,验证@AspectJ标注等
		validate(aspectClass);

		// 延迟加载相关.
		MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
				new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

		List<Advisor> advisors = new LinkedList<>();
		for (Method method : getAdvisorMethods(aspectClass)) {
		// 我们重点关注这一行,可以看到的是
		// declarationOrderInAspect参数入参为 advisors.size(),这意味着
		// 这与Advisor之后的Order排序有着密切的关系,从赋值来看,顺序为Advisor声明的顺序
			Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), 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);
		}

		// 检查是否又被@DeclareParents标注的属性.
		for (Field field : aspectClass.getDeclaredFields()) {
			Advisor advisor = getDeclareParentsAdvisor(field);
			if (advisor != null) {
				advisors.add(advisor);
			}
		}

		return advisors;
	}

  @DeclareParents用来标注对象属性,可以为对象添加一个新方法。继续追踪调用链,查看如何创建的切入点Advisor:

——>	public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
			int declarationOrderInAspect, String aspectName) {
		// 1、超类@AspectJ标注等验证
		validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
		// 2、获取切入点配置信息。说白了,就是在查找方法上是否有AOP切入点相关的注解标注
		AspectJExpressionPointcut expressionPointcut = getPointcut(
				candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
		// 在这里将过滤掉所有没有切入点配置的方法
		if (expressionPointcut == null) {
			return null;
		}
		// 3、构造切入点Advisor,关注到declarationOrderInAspect的赋值
		return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
				this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
	}

	// 看看Spring是如何获取到方法上面的切入点的
——>	private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
		// 查找方法是否有被@AspectJ类注解标注
		AspectJAnnotation<?> aspectJAnnotation =
				AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
		if (aspectJAnnotation == null) {
			return null;
		}
		// 构造成切入点对象
		AspectJExpressionPointcut ajexp =
				new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
		ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
		if (this.beanFactory != null) {
			ajexp.setBeanFactory(this.beanFactory);
		}
		return ajexp;
	}

——>	protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
		// 看到熟悉的AOP切入点注解:@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut
		Class<?>[] classesToLookFor = new Class<?>[] {
				Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
		// 若方法被其中某个标注,则为切入点
		for (Class<?> c : classesToLookFor) {
			AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
			if (foundAnnotation != null) {
				return foundAnnotation;
			}
		}
		// 非切入点方法,则会被过滤掉
		return null;
	}

  最终,根据AOP的切入点注解过滤出来的切面方法,将被构建成,InstantiationModelAwarePointcutAdvisorImpl。
InstantiationModelAwarePointcutAdvisorImpl类图
  查看它的类图可以发现其实现了Order接口,我们知道Order接口是Spring排序用的,这在我们讲解IOC时提到过,接下来我们就看一下,Spring是如何对这些切入点进行排序的?这种排序可以保证方法最终的执行顺序吗?

Advice初排序的调用链源码解析

  回到我们寻找所有Advice代码处findEligibleAdvisors,接着进行初排序分析。

	protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
		AnnotationAwareOrderComparator.sort(advisors);
		return advisors;
	}

	// AspectJAwareAdvisorAutoProxyCreator类中的实现
——>	protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
		List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors =
				new ArrayList<>(advisors.size());
		for (Advisor element : advisors) {
			partiallyComparableAdvisors.add(
					new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
		}
		// 根据Order值进行排序,可通过@Order进行设置,order的值越小其优先级越高
		List<PartiallyComparableAdvisorHolder> sorted =
				PartialOrder.sort(partiallyComparableAdvisors);
		if (sorted != null) {
			List<Advisor> result = new ArrayList<>(advisors.size());
			for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
				result.add(pcAdvisor.getAdvisor());
			}
			return result;
		}
		else {
			return super.sortAdvisors(advisors);
		}
	}

  可以看到,最终Order排序与切入点方法的执行顺序并不相同,那么Spring 又是如何保证切入点方法与代理方法的执行顺序的呢,也就是谁前谁后?

递归实现的调用链源码分析

  通过以上代码分析,我们了解到我们可以通过@Order注解标注切面的执行顺序,order的值越小其优先级越高。

  Advisor如何层层传递调用的?这一切都依仗于责任链设计模式。

  createProxy()经过层层调用将根据Bean实例是否是接口实现类判断采用JDK或CgLib动态代理,被代理的对象实例的方法在执行时,将会执行invoke()或intercept()方法。(如有疑问,请先了解AOP原理)

  现在,我们就从invoke()/intercept()方法开始分析,调用链究竟如何实现的。

  不论是采用JDK或是CgLib,你会发现在它们各自的代理方法中都包括如下这段代码,而这就是AOP Advisor 调用链的入口。

			// 获取当前代理的方法的所有Advice。
			// 如果你细细研究,会发现在getInterceptorsAndDynamicInterceptionAdvice中,有一段代码,
			// 将所有的AOP切面Advice转换成了MethodInterceptor接口。
			// MethodInterceptor则是Spring所有切入点方法的祖先。
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// 通过反射技术创建调用链执行方法...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 现在执行所有的拦截器和切面Advice.
				retVal = invocation.proceed();
			}

——>	public Object proceed() throws Throwable {
		// 执行完所有Advice后执行代理方法,currentInterceptorIndex 是标识调用链位置的偏移量。
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
		// 获取下一个要执行的Advice
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// 执行动态方法匹配器
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
			// InterceptorAndDynamicMethodMatcher 为动态拦截器,根据运行时参数来决定拦截器是否生效,有动态拦截器当然也有静态拦截器,静态拦截器一般通过包名,类名,方法名 ,参数来确定静态拦截器是否对代理方法生效
				return dm.interceptor.invoke(this);
			}
			else {
				// 动态方法匹配失败,跳过此拦截器并调用链中的下一个Advice拦截器,递归执行。
				return proceed();
			}
		}
		else {
			// 执行静态拦截器,我们的AOP 切入点都是此类拦截器(包括@DeclareParents)。
			// 这里将this传入过去,就是为了实现递归责任链
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

  Ctrl+Alt+B 可以看到invoke(this)有非常多的实现,我们重点关注@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut的实现。

  首先,我们需要了解它们各自的执行时机:

   @Before:前置通知,在方法执行之前运行;
   @After:后置通知,在方法返回结果之后运行;
   @AfterReturning:返回结果通知,方法返回结果之后运行,也拦截返回的结果;
   @Around:环绕通知,运行方法执行;
   @AfterThrowing:异常通知,在方法引发异常之后运行。

	// MethodBeforeAdviceInterceptor 前置通知
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
		return mi.proceed();
	}

	// AspectJAfterAdvice 后置通知
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		finally {
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

	// AfterReturningAdviceInterceptor 返回结果通知
	public Object invoke(MethodInvocation mi) throws Throwable {
		Object retVal = mi.proceed();
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		return retVal;
	}

	// AspectJAroundAdvice 环绕通知
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (!(mi instanceof ProxyMethodInvocation)) {
			throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
		}
		ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
		ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
		JoinPointMatch jpm = getJoinPointMatch(pmi);
		// 具体实现方式参阅invokeAdviceMethod部分代码。
		return invokeAdviceMethod(pjp, jpm, null, null);
	}

	// AspectJAfterThrowingAdvice 异常通知
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		catch (Throwable ex) {
			if (shouldInvokeOnThrowing(ex)) {
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}

   最终,Spring通过层层传递的this进行递归调用proceed(),通过currentInterceptorIndex偏移量记录调用链执行位置,通过反射调用目标方法,进而实现层层传递,层层调用的责任链模型。

AOP 调用链总结

   1、第一步,Spring IOC Bean后置处理器执行阶段,解析被@AspectJ注解标注的实例;

   2、第二步,解析@AspectJ标注的实例,这个过程是解析器内部的切入点、以及切面方法,最终将解析成MethodInterceptor接口1(当然这个过程还包括Spring其他拦截器的解析)。主要解析@Before、@Around、@After、@AfterReturning、@AfterThrowing、@Pointcut注解;

   3、第三步,根据order值,对MethodInterceptor排序,order的值越小其优先级越高。MethodInterceptor的order值等于它们的声明(加载)顺序,当然我们也可以通过@Order注解标注他们的顺序,Order的大小决定它们最终的执行顺序;

   4、第四步,当被代理类方法执行前,调用拦截链逻辑,主要依据下图所示实现。

   4.1、使用一个列表(interceptorsAndDynamicMethodMatchers)存放排序后的MethodInterceptor;
   4.2、使用一个偏移量(currentInterceptorIndex)标识当前调用链执行下标;
   4.3、递归过程中传递this关键字保证以上两个变量的全局一致;
   4.4、切入点逻辑依照不同类型的MethodInterceptor而选择。例如,前置通知,在方法之前执行等;
   4.5、层层传递,责任调用。前一个切面代码执行完交由下一个切面执行,形成链状模型。

Spring AOP 递归调用链模型

事务为何会失效?

public class TestTransactional {
    @Transactional(propagation = Propagation.REQUIRED)
    public void A() {
        User user = new User("chunsoft");
        userMapper.insertSelective(user);
        if (true) {
            throw new RuntimeException("抛异常");
        }
    }
    
    public void B() {
        this.A();
    }
}

   参考实例给出的代码,这里的事务为何会失效呢?我们先查看一下事务的执行代码,它也是MethodInterceptor的一种实现,上面我们介绍的AOP切面执行逻辑是一样的,它的名字叫做TransactionInterceptor。

	public Object invoke(MethodInvocation invocation) throws Throwable {
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// 执行事务逻辑...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}

	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// 如果transaction属性为null,则该方法为非事务处理。.
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		// 创建事务管理器
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// 开启事务
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// 调用链中的下一个拦截器.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 事务回滚
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				// 重置TransactionInfo ThreadLocal
				cleanupTransactionInfo(txInfo);
			}
			// 提交
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
		else {
			// 省略CallbackPreferringPlatformTransactionManager部分代码……
		}
	}

   可以看到,最终事务通过反射进行调用,但若@Transactional标注的方法在同类内被调用,则事务不会生效,因为Jdk/CgLib代理是基于对象的,需要在解析切入点是解析@Transactional注解的,同类调用,则代理不生效,事务不生效。


  1. 这里之所以说解析成MethodInterceptor而不是Advice,是因为MethodInterceptor继承了Advice,便于我们理解记忆。 ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值