Spring系列之AOP分析之對通知方法的執行過程(九)
轉載請註明出處:https://blog.csdn.net/zknxx/article/details/80261327
我們在上一篇文章中說到了前置通知的方法呼叫AspectJMethodBeforeAdvice#before,在這個before方法中又呼叫了invokeAdviceMethod這個方法,invokeAdviceMethod這個方法在AspectJMethodBeforeAdvice的父類AbstractAspectJAdvice中。AbstractAspectJAdvice這個是Aspect的所有通知型別的共同父類。關於AbstractAspectJAdvice中的invokeAdviceMethod方法,有兩個過載的方法。前置通知、後置通知、異常通知、後置返回通知都是用的AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)這個方法,環繞通知用的是:AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)這個方法。這兩個過載方法的區別是:後置通知呼叫的方法多了一個JoinPoint的引數。
invokeAdviceMethod方法的原始碼如下:
//這三個引數 JoinPointMatch 都是相同的
//returnValue 當執行後置返回通知的時候 傳值 其他為null
//Throwable 當執行後置異常通知的時候 傳值,其他為null
protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}
//過載的方法 這個 JoinPoint 是ProceedingJoinPoint
protected Object invokeAdviceMethod(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable t)
throws Throwable {
return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
}
我們先看getJoinPoint這個方法,其原始碼如下:
protected JoinPoint getJoinPoint () {
return currentJoinPoint();
}
public static JoinPoint currentJoinPoint() {
//這裡就不用再多說了 獲取當前的MethodInvocation 即ReflectiveMethodInvocation的例項
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
//JOIN_POINT_KEY 的值 為 JoinPoint.class.getName()
//從ReflectiveMethodInvocation中獲取JoinPoint 的值
//這裡在第一次獲取的時候 獲取到的 JoinPoint是null
//然後把下面建立的MethodInvocationProceedingJoinPoint放入到ReflectiveMethodInvocation的userAttributes中
//這樣在第二次獲取的是 就會獲取到這個 MethodInvocationProceedingJoinPoint
JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
if (jp == null) {
jp = new MethodInvocationProceedingJoinPoint(pmi);
pmi.setUserAttribute(JOIN_POINT_KEY, jp);
}
return jp;
}
下面我們來看一下argBinding這個方法的作用和內容。從名字我們可以猜測這個方法的作用應該是進行引數繫結用的,我們來看一下:
protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) {
calculateArgumentBindings();
// AMC start
Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
int numBound = 0;
//這個預設值是 -1 重新賦值是在calculateArgumentBindings中進行的
if (this.joinPointArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
numBound++;
}
else if (this.joinPointStaticPartArgumentIndex != -1) {
adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
numBound++;
}
//這裡主要是取通知方法中的引數型別 是除了 JoinPoint和ProceedingJoinPoint引數之外的引數
//如異常通知引數 返回通知引數
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++;
}
}
// binding from returning clause
//後置返回通知引數
if (this.returningName != null) {
Integer index = this.argumentBindings.get(this.returningName);
adviceInvocationArgs[index] = returnValue;
numBound++;
}
// binding from thrown exception
//異常通知引數
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 synchronized 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要放在通知方法的第一個引數
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) {
numUnboundArgs--;
}
else if (maybeBindJoinPointStaticPart(parameterTypes[0])) {
numUnboundArgs--;
}
//這裡是對其他的引數的處理 處理過程還是比較複雜一點的 這裡不再多說了。
if (numUnboundArgs > 0) {
// need to bind arguments by name as returned from the pointcut match
bindArgumentsByName(numUnboundArgs);
}
this.argumentsIntrospected = true;
}
這裡還有再說一下AbstractAspectJAdvice這個類的建構函式,這個類只有這一個建構函式
public AbstractAspectJAdvice(
Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
//通知方法不能為空
Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
//切面類
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
//通知方法的名字
this.methodName = aspectJAdviceMethod.getName();
//通知方法引數
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
//通知方法
this.aspectJAdviceMethod = aspectJAdviceMethod;
//切點類
this.pointcut = pointcut;
//切面例項的工廠類
this.aspectInstanceFactory = aspectInstanceFactory;
}
在建立通知類例項的時候,進行了上面的賦值的動作,把和通知相關的方法都傳了進來。最後我們來看一下invokeAdviceMethodWithGivenArgs這個方法的內容:
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
//判斷通知方法是否有引數
if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
actualArgs = null;
}
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// TODO AopUtils.invokeJoinpointUsingReflection
//反射呼叫通知方法
//this.aspectInstanceFactory.getAspectInstance()獲取的是切面的例項
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("Mismatch on arguments to advice method [" +
this.aspectJAdviceMethod + "]; pointcut expression [" +
this.pointcut.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}