AOP MethodInterceptor

警告
本文最后更新于 2020-12-02,文中内容可能已过时。

spring源码系列文章,示例代码的中文注释,均是 copy 自 https://gitee.com/wlizhi/spring-framework

链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。


1 代理方法执行回顾

在代理对象方法被调用时,会获取到执行链,将其封装在 ProxyMethodInvocation 中,调用 proceed(),通过递归方式,依次调用执行链中的通知方法节点,最终会调用到代理方法,然后回转到调用处。回转过程中可能存在后置通知方法的节点调用,其顺序与前置的调用刚好相反。

多个切面通知方法,执行顺序实际上是环环相扣的,被代理方法在中间,与内存模型的栈有点类似,先进后出。

业务开发中我们自定义的切面共有五种,实际上并不是每种都常用。

列举这五种通知方法的MethodInterceptor实现类和对应注解:

  • MethodBeforeAdviceInterceptor –> @Before
  • AspectJAroundAdvice –> @Around
  • AspectJAfterAdvice –> @After
  • AspectJAfterReturningAdvice –> @AfterReturning
  • AspectJAfterThrowingAdvice –> @AfterThrowing

2 MethodBeforeAdviceInterceptor

MethodBeforeAdviceInterceptor.invoe() 中,封装了 MethodBeforeAdvice,在这个 Advice 中,通过调用 before(),来完成通知方法的调用。

首先会调用 before(),接着,调用 MethodInvocation.proceed()。继续向下递归,直到执行链的末尾,调用被代理方法,然后返回。

源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
	// 封装了前置通知方法的具体执行内容。
	private final MethodBeforeAdvice advice;
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 调用advice.before(),执行内容封装的前置通知方法。调用MethodInvocation.proceed(),火炬传递。实际上就是一个递归调用。
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}
}

来到 before() :

1
2
3
4
5
6
7
8
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {

    @Override
	public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
		// 前置增强知识调用切面通知方法,并未理会参数中传递的目标对象、参数列表、以及方法
		invokeAdviceMethod(getJoinPointMatch(), null, null);
	}
}

参数 JoinPointMatch 暂且忽略。

继续往下跟踪源码:

1
2
3
4
5
6
7
8
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
	protected Object invokeAdviceMethod(
			@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
			throws Throwable {
		// 获取到绑定参数,调用通知方法。
		return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
	}
}

上面源码中有两个关键方法,invokeAdviceMethodWithGivenArgs()、argBinding()。它们的作用分别是执行通知方法、获取通知方法的参数。

由于五种拦截器调用和获取参数绑定的逻辑都是相同的方法,这两个方法放到后面列出。

3 AspectJAroundAdvice

进入 invoke() 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
		ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
		JoinPointMatch jpm = getJoinPointMatch(pmi);
		// 执行环绕通知方法,注意,这里相对AspectJAfterAdvice,多传递了了一个参数pjp,这里是一个MethodInvocationProceedingJoinPoint,
		// 内部封装了代理调用对象ProxyMethodInvocation。也就是说,在环绕通知方法中,是可以拿到ProxyMethodInvocation,并可以在其方法任意位置
		// 进行执行链的火炬传递。
		return invokeAdviceMethod(pjp, jpm, null, null);
	}
}

来到 lazyGetProceedingJoinPoint():

1
2
3
4
5
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
    protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {
		return new MethodInvocationProceedingJoinPoint(rmi);
	}
}

new MethodInvocationProceedingJoinPoint(rmi) 参数中,传递了一个 ProxyMethodInvocation,这个类是用来调用执行链中通知方法的。这里把它封装到了ProceedingJoinPoint中。

在 invokeAdviceMethod() 中将这个创建的 ProceedingJoinPoint 传递下去。我们在使用 @Around 时,会将 ProceedingJoinPoint 作为通知方法的参数,这个参数就来自这里。

接着,会执行到 invokeAdviceMethod():

1
2
3
4
5
6
7
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
			@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
		// 获取到绑定参数,调用通知。
		return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
	}
}

最终,这里有两个方法调用,invokeAdviceMethodWithGivenArgs()、argBinding()。

4 AspectJAfterAdvice

invoke() 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class AspectJAfterAdvice extends AbstractAspectJAdvice
		implements MethodInterceptor, AfterAdvice, Serializable {
    @Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			// 先执行执行链,当所有执行链中的通知方法,及被代理方法执行完毕,会继续往下走。
			// 最终,在finally语句块中,调用后置通知方法。卸载finally语句块里,是让在执行链中及被代理方法执行完毕,但还未返回时,执行后置通知方法。
			// 这里有点绕。 A(around) -> B(after) -> C(around),假设有这三个切面组成执行链,他们执行顺序按照前面的排序。
			// 执行顺序是这样的:A环绕前置通知 -> B invoke中通知调用执行链下一个节点(这里没有实际的执行内容,只是做一个火炬传递) -> C环绕前置通知 -> C环绕后置通知 -> B后置通知(这时候B中调用完,返回前掉finally语句块) -> A环绕后置通知。
			return mi.proceed();
		}
		finally {
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}
}

先将执行链中剩余节点执行完毕,在执行完之后,方法返回之前,finally块中调用了后置通知方法。仔细看下源码中注释部分。

接着到 invokeAdviceMethod(),与前面的一致,都是到这个方法:

1
2
3
4
5
6
7
8
9
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {

    protected Object invokeAdviceMethod(
			@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
			throws Throwable {
		// 获取到绑定参数,调用通知方法。
		return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
	}
}

5 AfterReturningAdviceInterceptor

先调用执行链中剩余节点,拿到返回值之后,再调用 afterReturning() 通知方法。afterReturning() 中一样会调到 invokeAdviceMethod()。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 首先执行执行链方法,当执行链中的通知方法、及被代理方法都执行完毕。才执行afterReturning方法。
		Object retVal = mi.proceed();
		// 执行afterReturning通知方法。
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
		return retVal;
	}
}

afterReturning() 源码:

1
2
3
4
5
6
7
8
9
public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice
		implements AfterReturningAdvice, AfterAdvice, Serializable {
    @Override
	public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
		if (shouldInvokeOnReturnValueOf(method, returnValue)) {
			invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
		}
	}
}

这里会传入 returnValue。

6 AspectJAfterThrowingAdvice

调用执行链中剩余的节点通知,当这些通知方法抛出异常,就执行当前通知方法。invokeAdviceMethod() 中会调用 invokeAdviceMethod()。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
		implements MethodInterceptor, AfterAdvice, Serializable {
    @Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			// 调用执行链,如果执行链中的剩余节点中,有任何一个节点抛出异常,在这里捕获,在catch语句块传递给afterThrowing通知方法。
			return mi.proceed();
		}
		catch (Throwable ex) {
			if (shouldInvokeOnThrowing(ex)) {
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}
}

这里传入了异常对象。

7 ArgBinding() 参数绑定

以下是 argBinding() 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
			@Nullable Object returnValue, @Nullable Throwable ex) {
		// 计算参数绑定
		calculateArgumentBindings();

		// AMC start
		Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
		int numBound = 0;
		// 如果存在参数JointPoint,则在这里放入参数。
		if (this.joinPointArgumentIndex != -1) {
			adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
			numBound++;
		}// 如果存在参数JoinPoint.StaticPart,则在这里绑定
		else if (this.joinPointStaticPartArgumentIndex != -1) {
			adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
			numBound++;
		}
		// 省略...

		return adviceInvocationArgs;
	}
}

adviceInvocationArgs[this.joinPointArgumentIndex] = jp 设置了 JoinPoint 参数。

看一下calculateArgumentBindings() 都做了什么:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    public final synchronized void calculateArgumentBindings() {
		if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
			return;
		}

		int numUnboundArgs = this.parameterTypes.length;
		// 获取切面通知方法的参数列表
		Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
		// 从这里可以看出,方法的第一个参数必须是JoinPoint类型的
		if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
				maybeBindJoinPointStaticPart(parameterTypes[0])) {
			numUnboundArgs--;
		}
		// 类似这种 @Around("pc1()") around(ProceedingJoinPoint joinPoint)
		// 方法只有一个参数,numUnboundArgs是0,就不需要参数绑定了
		if (numUnboundArgs > 0) {
			// 需要通过切入点匹配返回的名称绑定参数
			// need to bind arguments by name as returned from the pointcut match
			bindArgumentsByName(numUnboundArgs);
		}

		this.argumentsIntrospected = true;
	}
}
  1. 获取通知方法参数的个数赋值给 numUnboundArgs,获取到通知方法的所有参数类型。
  2. 拿第一个参数,去判断是否符合条件,如果符合条件,将变量 numUnboundArgs 减一。
  3. 如果方法中参数有多个,需要调用 bindArgumentsByName()。

看一下 maybeBindJoinPoint(),另外两个方法与这个类似:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    private boolean maybeBindJoinPoint(Class<?> candidateParameterType) {
		if (JoinPoint.class == candidateParameterType) {// 如果候选的参数类型是JoinPoint类型,则返回true,标识绑定成功。
			this.joinPointArgumentIndex = 0;
			return true;
		}else {
			return false;
		}
	}
}

bindArgumentsByName会调用到bindExplicitArguments() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    private void bindExplicitArguments(int numArgumentsLeftToBind) {
		Assert.state(this.argumentNames != null, "No argument names available");
		this.argumentBindings = new HashMap<>();
		// 获取参数的数量
		int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount();
	    // 省略...
		// 参数索引偏移量,也就是前面已经绑定过的参数,需要跳过,这里记录的是跳过已绑定参数后的下一个索引值。
		int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
		for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
			this.argumentBindings.put(this.argumentNames[i], i);
		}  // 省略部分代码...
		// 把参数对应的名称和对应的参数的类型设置到pointcut中
		// configure the pointcut expression accordingly.
		configurePointcutParameters(this.argumentNames, argumentIndexOffset);
	}
}

获取到参数索引偏移量,这个偏移量是指前面已绑定的 JoinPoint。 来到 configurePointcutParameters() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {

    private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) {
		int numParametersToRemove = argumentIndexOffset;
		if (this.returningName != null) {
			numParametersToRemove++;
		}
		if (this.throwingName != null) {
			numParametersToRemove++;
		}
		// 这里装的是参数名称,排除了已绑定的JoinPoint
		String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];
		Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];
		Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();

		int index = 0;
		for (int i = 0; i < argumentNames.length; i++) {
			if (i < argumentIndexOffset) {
				continue;
			}
			if (argumentNames[i].equals(this.returningName) ||
				argumentNames[i].equals(this.throwingName)) {
				continue;
			}
			// 将参数名称,封装到pointcutParameterNames中,将参数类型封装到pointcutParameterTypes中。
			pointcutParameterNames[index] = argumentNames[i];
			pointcutParameterTypes[index] = methodParameterTypes[i];
			index++;
		}
		// 将未绑定的参数名称数组、参数类型数组,封装到成员变量 pointcut。
		this.pointcut.setParameterNames(pointcutParameterNames);
		this.pointcut.setParameterTypes(pointcutParameterTypes);
	}
}

跳过已绑定的位置,从后面的索引开始,将通知方法中的参数名称按照下标索引封装到数组 pointcutParameterNames 中,将参数类型封装到 pointcutParameterTypes 中。

8 InvokeAdviceMethodWithGivenArgs() 通知方法调用

通过反射,根据绑定的参数,方法,切面类,进行通知方法调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
    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
        return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
	}
}

不同种类的 MethodInterceptor,有不同的实现,而最终调用时,都是通过反射进行方法调用的。