Spring AOP-代理对象增强目标方法的流程,通知方法到底什么执行顺序

目录

前言

流程图        ​

以 MethodBeforeAdviceInterceptor 为例探讨递归流程         ​

通知方法执行的顺序是什么呢?

总结


前言

        本文讨论的内容为代理对象如何对切点方法进行增强,即如何协调执行各个通知方法来实现对连接点方法的增强。所以本文的前提是通知方法已经排好序,代理对象已经创建出来了。现在的时间点是业务层调用了切点方法,问题是切点方法和各个通知方法如何增强。此外因为 AOP 场景下 JdkDynamicAopProxy 用的比较多,所以本文也是分析 JdkDynamicAopProxy 的增强逻辑

流程图        

jdk 动态代理执行流程图https://www.processon.com/view/link/61505b93637689167d1055a3icon-default.png?t=L892https://www.processon.com/view/link/61505b93637689167d1055a3       

        上面的流程主要有两步,第一步是解析 advisor ,通过 advisor 中的 pointCut 来进行筛选寻找跟当前方法匹配的 advisor。

        这里有一点需要注意一下,pointCut 进行匹配首先要经过类匹配和方法匹配这两步,但是我们在代码里看到 config.isPreFiltered() 这个判断条件。还记得这些 advisor 是从哪里来的吗?是在尝试给 bean 创建动态代理的时候跟切面解析出来的 advisor 匹配得来的。也就是说这些 advisor 一定是跟当前类匹配的。所以在创建代理对象的时候设置 preFiltered 为 true,目的也是为了方便在现在执行增强的时候只需要看方法匹配不匹配就好了,类匹配这一步就跳过了,提高了效率。

        获取到跟当前方法匹配的 advisor 之后,需要把其中的 advice 取出来转换为 MethodInterceptor 类型的拦截器,其中 AspectJAroundAdvice,AspectJAfterAdvice 和 AspectJAfterThrowingAdvice 这三种类型的 advice 本身就是 MethodInterceptor 类型的,所以就不用再转换了。但是 AspectJMethodBeforeAdvice 和 AspectJAfterReturningAdvice 需要借助转换器分别转变为 MethodBeforeAdviceInterceptor 和 AfterReturningAdviceInterceptor 类型的拦截器。

        为什么要转变为 MethodInterceptor 类型呢,因为 MethodInceptor 类型拦截器都有 invoke 方法,如果所有拦截器都通过执行该方法实现增强,处理起来也就更方便,也就是为了后面递归调用拦截器链。需要补充的是,虽然 AspectJMethodBeforeAdvice 和 AspectJAfterReturningAdvice 都转换为了 MethodInterceptor 类型,但是也只是对 advice 进行了一层封装,对目标方法增强的时候也还是通过调用本来的 advice 的增强方法来实现的。 

        获取到拦截器链之后,就通过递归调用 ReflectiveMethodInvocation.proceed() 方法来取出一个个拦截器进行拦截增强,当所有拦截器方法都执行了之后才会执行连接点方法。

以 MethodBeforeAdviceInterceptor 为例探讨递归流程         

         递归调用拦截器链简单来说就是先获取到一个拦截器,然后执行拦截器的增强方法,然后再调用 proceed 方法获取下一个拦截器,如此循环,直至拦截器的增强方法都执行之后再执行目标方法,上图已经很清晰了。

        有一点需要注意的是在目标方法执行之前所有拦截器的增强方法都执行了,可是并不是执行完毕的意思,这也就是我在第一张流程图里调用目标方法那里没有用结束符的原因。分别看一下几个拦截器的增强方法

    // AspectJAroundAdvice 拦截器调用增强方法的方法
    protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
		Object[] actualArgs = args;
		if (this.aspectJAdviceMethod.getParameterCount() == 0) {
			actualArgs = null;
		}
		try {
			ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
			// TODO AopUtils.invokeJoinpointUsingReflection
            // 在这里调用 @Around 通知方法
			return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("Mismatch on arguments to advice method [" +
					this.aspectJAdviceMethod + "]; pointcut expression [" +
					this.pointcut.getPointcutExpression() + "]", ex);
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}
    
    //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;
	}
 
    //AspectJAfterThrowingAdvice
    public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		catch (Throwable ex) {
			if (shouldInvokeOnThrowing(ex)) {
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}

        递归过程其实也就是一个个顺序的执行上面的拦截器方法,递归改成顺序执行的话就是下面这样

try {

	// @Around 通知在主动调用目标方法前可以添加的增强逻辑

	//执行 @Before 的通知方法
	this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
	try {
			try {
				//执行目标方法
				return invokeJoinpoint();
			} catch (Throwable ex) {
				if (shouldInvokeOnThrowing(ex)) {
					//执行 @AfterThrowing 的通知方法
					invokeAdviceMethod(getJoinPointMatch(), null, ex);
				}
				throw ex;
			}
			//执行 @AfterReturning 的通知方法
			this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
	} finally {
		//执行 @After 的通知方法
		invokeAdviceMethod(getJoinPointMatch(), null, null);
	} 

	// @Around 通知调用目标方法后还可以再继续操作
	
} catch (IllegalArgumentException ex) {
	throw new AopInvocationException("Mismatch on arguments to advice method [" +
			this.aspectJAdviceMethod + "]; pointcut expression [" +
			this.pointcut.getPointcutExpression() + "]", ex);
} catch (InvocationTargetException ex) {
	throw ex.getTargetException();
}

         由上可以得出如下结论:

  1. @Around 通知最先执行,如果在 @Around 通知主动调用目标方法之前报错了,那么后面拦截器方法包括目标方法都不会执行了。当然就算不报错也可以进行一些校验操作,不想让流程往下面走直接返回也行。
  2. @Before 通知的功能跟 @Around 前半部分类似,所以一般 @Before 不会跟 @Around 一起使用。
  3. 目标方法在 @Before 通知之后执行。如果目标方法报错会触发 @AfterThrowing 通知的增强方法,否则就会执行 @AfterReturning 的增强,可见这两个通知是不会同时执行的。
  4. @After 通知在 @AfterReturning 或 @AfterThrowing 之后执行,可见无论目标方法是否异常,该通知方法都会执行。所以可以做一些资源释放的工作或者一些记录工作。
  5. @After 通知执行完毕之后还会回到 @Around 通知方法,所以如果想做一些事情还能继续做。

通知方法执行的顺序是什么呢?

        我们上一篇通过分析 advisor 的排序得出了 Around->Before->After->AfterReturning->AfterThrowing 的结论,可是经过本文的分析,这样的说法其实不算很准确,因为这个顺序是指各个 advisor 或者 advice 再或者是拦截器的执行顺序,拦截器会执行吗?一定会,但是通知方法一定会执行吗?不一定,比如我们本文的分析就得出了 @AfterReturning 和 @AfterThrowing 的通知方法只会有一个执行。此外拦截器先执行那么拦截器方法也会先执行吗?也不一定,比如说 After,AfterReturning,AfterThrowing 的拦截器都先于目标方法执行,但是真正执行的时候却是目标方法先执行,然后是 AfterReturning 或者 AfterThrowing 的通知方法,最后才是 After 的通知方法。

        综上所述,如果问题是增强方法的执行顺序,那就是 Around -> Before -> 目标方法 -> AfterReturning | AfterThrowing -> After, 此外还要注意的是 Around 通知方法最先开始执行,却是最后才执行结束。

        如果是拦截器或者advisor的执行顺序,那么还是上篇文章分析的结果 Around->Before->After->AfterReturning->AfterThrowing。

        上篇文章留了个问题就是 advisor 为什么要这么排序,其实也就是因为每个拦截器都已经写了一些处理逻辑,为了能满足按照 Around -> Before -> 目标方法 -> AfterReturning | AfterThrowing -> After 的顺序来执行通知方法,才选择了那样进行排序。

总结

        没有总结,就这样吧, Have a nice day!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值