likes
comments
collection
share

Spring AOP 通知的实现——AOP 源码解析(二)

作者站长头像
站长
· 阅读数 27

一、引言

上文结尾,我们在结尾猜测了Spring AOP的实现原理。本期,我们就通过源码验证一下,我们使用自底向上的分析方式,不管AOP如何引入,代理对象如何创建的。直接看代理方法是如何运行的,然后一层一层的向上分析。

二、通知

本期我们先分析下通知是如何实现的。Spring AOP支持Before/After等各种不同的通知,而不同的通知,被抽象为统一的接口,MethodInterceptor(方法拦截器),下面为通知实现的类图。

Spring AOP 通知的实现——AOP 源码解析(二)

简单说明:

  1. MethodInterceptor继承自Advice和Interceptor接口,不过这并不重要,因为这两个接口都没有任何方法,相当于一种“标记”接口。MethodInterceptor有个invoke方法,这个就是该接口的核心,用于通知的调用。
  2. AbstractAspectJAdvice,抽象类,各种通知都继承自该接口,有点类似模板方法,定义了一些通用方法。
  3. 如图所示,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
评论
请登录