由于md文档部分hugo插件语法不兼容,建议访问作者网站查阅文章:wlizhi.cc
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()。继续向下递归,直到执行链的末尾,调用被代理方法,然后返回。
源码如下:
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() :
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 暂且忽略。
继续往下跟踪源码:
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() 源码:
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():
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
protected