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;
}
}
|
- 获取通知方法参数的个数赋值给 numUnboundArgs,获取到通知方法的所有参数类型。
- 拿第一个参数,去判断是否符合条件,如果符合条件,将变量 numUnboundArgs 减一。
- 如果方法中参数有多个,需要调用 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,有不同的实现,而最终调用时,都是通过反射进行方法调用的。