Spring AOP 通知的实现——AOP 源码解析(二)
一、引言
上文结尾,我们在结尾猜测了Spring AOP的实现原理。本期,我们就通过源码验证一下,我们使用自底向上的分析方式,不管AOP如何引入,代理对象如何创建的。直接看代理方法是如何运行的,然后一层一层的向上分析。
二、通知
本期我们先分析下通知是如何实现的。Spring AOP支持Before/After等各种不同的通知,而不同的通知,被抽象为统一的接口,MethodInterceptor(方法拦截器),下面为通知实现的类图。
简单说明:
- MethodInterceptor继承自Advice和Interceptor接口,不过这并不重要,因为这两个接口都没有任何方法,相当于一种“标记”接口。MethodInterceptor有个invoke方法,这个就是该接口的核心,用于通知的调用。
- AbstractAspectJAdvice,抽象类,各种通知都继承自该接口,有点类似模板方法,定义了一些通用方法。
- 如图所示,Around、AfterThrowing、After这三种通知,直接实现了MethodInterceptor接口,而AfterReturning、Before通知,则没有,还需要分别通过两个Interceptor代理,进而实现MethodInterceptor(至于为什么,我也不知道),通过名字也能对应出来,各种通知的实现是哪个:
- @Before - MethodBeforeAdviceInterceptor
- @After - AspectJAfterAdvice
- @AfterReturning - AfterReturningAdviceInterceptor
- @AfterThrowing - AspectJAfterThrowingAdvice
- @Around - AspectJAroundAdvice
2.1 invoke方法
MethodInterceptor的核心,就是这个invoke方法,各种通知的核心,也就是实现这个invoke方法。
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
这个invoke方法,有个MethodInvocation的参数,MethodInvocation从名字就可以看出来,这是一个方法执行的抽象,可以使用该对象,控制方法的执行。下面我们看下具体通知的实现,不难猜到,before就是先执行通知方法,再执行后续的拦截器链,after就是在拦截器链之后执行通知方法。
@Before - MethodBeforeAdviceInterceptor的invoke方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 先执行通知方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
// 再执行原来的方法
return mi.proceed();
}
源码出自:MethodBeforeAdviceInterceptor#invoke
@After - AspectJAfterAdvice的invoke方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//先执行原方法
return mi.proceed();
}finally {
//finally中执行通知方法
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
源码出自:AspectJAfterAdvice.invoke(MethodInvocation)
不难想象:
- @AfterReturning,也是在执行proceed之后执行通知方法,但是必须是正常执行完成(没有异常抛出)才可以。
- @AfterThrowing,会try住proceed方法,并在catch中执行通知方法,只有发生异常的时候才会执行;
- @Around ,完全交给通知去处理,并没有调用proceed方法,是否执行proceed方法,甚至执行几次,都交给通知自己去决定。
2.2 invokeAdviceMethod - 通知方法的执行
各种拦截器,会在合适的时机,执行通知方法,这几种拦截器,最终都是执行invokeAdviceMethod方法,这是抽象类AbstractAspectJAdvice
的方法:
protected Object invokeAdviceMethod(
@Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
// As above, but in this case we are given the join point.
protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
}
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
//反射调用,省略代码
}
本质上,就是反射调用我们自定义的通知方法,核心是使用什么样的参数调用,也就argBinding
这个方法:
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;
// joinPoint参数
if (this.joinPointArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
numBound++;
}
// 也可以使用JoinPoint.StaticPart作为参数,仅能获取到JoinPoint中不变的参数,比如方法签名
else if (this.joinPointStaticPartArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
numBound++;
}
if (!CollectionUtils.isEmpty(this.argumentBindings)) {
// binding from pointcut match 不知道什么时候走这个分支
if (jpMatch != null) {
PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();
for (PointcutParameter parameter : parameterBindings) {
String name = parameter.getName();
Integer index = this.argumentBindings.get(name);
adviceInvocationArgs[index] = parameter.getBinding();
numBound++;
}
}
// 设置返回值参数
if (this.returningName != null) {
Integer index = this.argumentBindings.get(this.returningName);
adviceInvocationArgs[index] = returnValue;
numBound++;
}
// 设置异常参数
if (this.throwingName != null) {
Integer index = this.argumentBindings.get(this.throwingName);
adviceInvocationArgs[index] = ex;
numBound++;
}
}
// 数量不一致,抛出异常
if (numBound != this.parameterTypes.length) {
throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
" arguments, but only bound " + numBound + " (JoinPointMatch " +
(jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");
}
return adviceInvocationArgs;
}
以上的代码还是比较简单的,就是根据 calculateArgumentBindings计算出来的参数位置,设置参数。
下面看看这参数是怎么计算出来的。
public final void calculateArgumentBindings() {
// The simple case... nothing to bind.
if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
return;
}
int numUnboundArgs = this.parameterTypes.length;
Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
// 可以看出来,JoinPoint/ProceedingJoinPoint/StaticPart必须是第一个,且三选一
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
maybeBindJoinPointStaticPart(parameterTypes[0])) {
numUnboundArgs--;
}
if (numUnboundArgs > 0) {
// need to bind arguments by name as returned from the pointcut match
// 处理其他参数,包括返回值、抛出的异常等参数,不再深入了
bindArgumentsByName(numUnboundArgs);
}
this.argumentsIntrospected = true;
}
2.3 AspectJAroundAdvice - 环绕通知的实现
我们特别的看下环绕通知的实现,环绕通知是把整个方法完全接管过来,能控制被代理方法的任何行为,包括是否执行代理方法,以及返回值。他的实现甚至没有调用proceed方法,直接调用的invoke方法,具体调用proceed方法的时机,交给具体的通知方法决定。
@Override
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);//使用ProceedingJoinPoint作为JoinPoint参数
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
}
有一点比较有趣,上面代码对MethodInvocation做了一层封装,变成了ProceedingJoinPoint,而ProceedingJoinPoint有个proceed方法,可以被环绕通知去调用,看下lazyGetProceedingJoinPoint做了什么:
protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {
return new MethodInvocationProceedingJoinPoint(rmi);
}
而MethodInvocationProceedingJoinPoint的proceed方法是这样的:
@Override
public Object proceed() throws Throwable {
return this.methodInvocation.invocableClone().proceed();
}
而invocableClone其实就是做了一个浅拷贝:
@Override
public MethodInvocation invocableClone(Object... arguments) {
// Force initialization of the user attributes Map,
// for having a shared Map reference in the clone.
if (this.userAttributes == null) {
this.userAttributes = new HashMap<>();
}
// Create the MethodInvocation clone.
try {
ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
clone.arguments = arguments;
return clone;
}
catch (CloneNotSupportedException ex) {
throw new IllegalStateException(
"Should be able to clone object of type [" + getClass() + "]: " + ex);
}
}
源码出自:ReflectiveMethodInvocation.invocableClone(Object...)
为什么要这样做呢?直接让调用methodInvocation的proceed方法不可以么?我们留下这个问题,下一章来解答。
系列所有文章
转载自:https://juejin.cn/post/7366086988350521355