SpringAOP source code analysis advice execution sequence (3)

In the previous chapter, we analyzed the order of advice in Aspect as Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class. Then what is the actual execution order of advice? What is the execution order between multiple aspects? This is the question we explore in this chapter.

Preparation

Since we need to know the execution order of advise, we must have Aspect. We still use the ThamNotVeryUsefulAspect created before. The code content is as follows:

@Component
@Aspect
public class ThamNotVeryUsefulAspect {<!-- -->
@Pointcut("execution(* com.qhyu.cloud.aop.service.QhyuAspectService.*(..))") // the pointcut expression
private void thamAnyOldTransfer() {<!-- -->} // the pointcut signature

@Before("thamAnyOldTransfer()")
public void before(){<!-- -->
System.out.println("tham Before method call");
}

@After("thamAnyOldTransfer()")
public void after(){<!-- -->
System.out.println("tham After method call");
}
\t
@AfterReturning("thamAnyOldTransfer()")
public void afterReturning(){<!-- -->
System.out.println("tham afterReturning");
}
\t
@AfterThrowing("thamAnyOldTransfer()")
public void afterThrowing(){<!-- -->
System.out.println("tham AfterThrowing");
}
\t
@Around("thamAnyOldTransfer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{<!-- -->
// start stopwatch
System.out.println("tham around before");
Object retVal = pjp.proceed();
// stop stopwatch
System.out.println("tham around after");
return retVal;
}
}

Pointcut (official website address) points to any method defined by the QhyuAspectService interface.

public interface QhyuAspectService {<!-- -->
void test();
}

@Component
public class QhyuAspectServiceImpl implements QhyuAspectService {<!-- -->

@Override
public void test() {<!-- -->
System.out.println("Execute my method");
}
}

Then just call the startup class

public class QhyuApplication {<!-- -->

public static void main(String[] args) {<!-- -->
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(AopConfig.class);
//test(annotationConfigApplicationContext);

aspectTest(annotationConfigApplicationContext);
//eventTest(annotationConfigApplicationContext);
//transactionTest(annotationConfigApplicationContext);

}
private static void aspectTest(AnnotationConfigApplicationContext annotationConfigApplicationContext) {<!-- -->
QhyuAspectService bean1 = annotationConfigApplicationContext.getBean(QhyuAspectService.class);
bean1.test();
}
}

At this point we are ready, and then we will develop source code level analysis.

JdkDynamicAopProxy

When wrapIfNecessary, a proxy object will be created for our class. There is no forced use of cglib. The proxyTargetClass default false in @EnableAspectJAutoProxy.

Since my QhyuAspectServiceImpl implements the QhyuAspectService interface, I use JdkDynamicAopProxy to create the proxy object. Therefore, when the test method of QhyuAspectServiceImpl is called, it will enter the invoke method of JdkDynamicAopProxy. The source code is as follows:

@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {<!-- -->
Object oldProxy = null;
boolean setProxyContext = false;

TargetSource targetSource = this.advised.targetSource;
Object target = null;

try {<!-- -->
if (!this.equalsDefined & amp; & amp; AopUtils.isEqualsMethod(method)) {<!-- -->
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined & amp; & amp; AopUtils.isHashCodeMethod(method)) {<!-- -->
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {<!-- -->
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque & amp; & amp; method.getDeclaringClass().isInterface() & amp; & amp;
method.getDeclaringClass().isAssignableFrom(Advised.class)) {<!-- -->
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}

Object retVal;

if (this.advised.exposeProxy) {<!-- -->
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}

// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);

// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {<!-- -->
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {<!-- -->
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
//Execute the interceptor chain
retVal = invocation.proceed();
}

// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null & amp; & amp; retVal == target & amp; & amp;
returnType != Object.class & amp; & amp; returnType.isInstance(proxy) & amp; & amp;
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {<!-- -->
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null & amp; & amp; returnType != Void.TYPE & amp; & amp; returnType.isPrimitive()) {<!-- -->
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {<!-- -->
if (target != null & amp; & amp; !targetSource.isStatic()) {<!-- -->
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {<!-- -->
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}

this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) is to get the advice of the aspects we sorted before, and the order is the order of the previous sorting.

ExposeInvocationInterceptor is an interceptor (Interceptor) in the Spring AOP framework, used to expose the current proxy object to the AopContext during method invocation.

AopContext is a tool class provided by Spring for obtaining the current proxy object. When using AOP for method interception, Spring will create an AopContext instance for each proxy object and set the current proxy object to the AopContext before the method is called. In this way, the current proxy object can be obtained through AopContext inside the method, and then other methods of the proxy can be called inside the method to realize mutual calls between methods.

The role of ExposeInvocationInterceptor is to set the current proxy object to the AopContext during method invocation. It is the first interceptor in the entire AOP interceptor chain, ensuring that the current proxy object can be obtained through AopContext in subsequent interceptors or aspects.

Why does every AOP need ExposeInvocationInterceptor? This is because the AOP framework needs to ensure that proxy objects are handled correctly during method calls. Since AOP is implemented through a dynamic proxy mechanism, the proxy object will be wrapped in an interceptor chain and processed through this chain in turn when the method is called. In order to correctly pass the current proxy object, you need to use ExposeInvocationInterceptor to set the proxy object to the AopContext before the method is called.

It should be noted that ExposeInvocationInterceptor is a unique interceptor of the Spring AOP framework and may not have a corresponding implementation in other AOP frameworks. It exists to support the AopContext functionality in Spring AOP to obtain the current proxy object in the AOP interceptor chain.

AspectJAroundAdvice, MethodBeforeAdviceInterceptor, AspectJAfterAdvice, AfterReturningAdviceInterceptor, AspectJAfterThrowingAdvice are our @Around, @Before, @After, @AfterReturning, @AfterThrowing.

Here comes the key point. Next, we will analyze ReflectiveMethodInvocation, AspectJAroundAdvice, MethodBeforeAdviceInterceptor, AspectJAfterAdvice, AfterReturningAdviceInterceptor, and AspectJAfterThrowingAdvice one by one.

ReflectiveMethodInvocation

This code is the proceed method in the ReflectiveMethodInvocation class. This proceed() method is an important method of the ReflectiveMethodInvocation class in the Spring framework. In AOP (Aspect Oriented Programming), interceptors are used to perform specific operations before, after, or around method calls. This proceed() method plays a key role in the method calling process. Let me break down the provided code:

  1. this.currentInterceptorIndex represents the index of the current interceptor. Initially, the index value is -1 and then increments early.

  2. This conditional statement checks if the current interceptor index is equal to the last index of the interceptor list. If it is, it means that the end of the interceptor chain has been reached, and the invokeJoinpoint() method is called to perform the actual method call and return its result.

  3. If the current interceptor index is not the last one, get the next interceptor or interceptor notification (interceptorOrInterceptionAdvice).

  4. The next conditional statement checks the type of interceptorOrInterceptionAdvice. If it is of type InterceptorAndDynamicMethodMatcher, it means it is an interceptor for dynamic method matching.

    • The interceptor’s dynamic method matcher is evaluated here (already evaluated in the static part and determined to be a match).
    • If the method matches successfully, the invoke(this) method of the interceptor is called and the result is returned.
    • If the method fails to match, skip the current interceptor and continue to call the next interceptor, which is achieved by recursively calling the proceed() method.
  5. If interceptorOrInterceptionAdvice is an ordinary interceptor (MethodInterceptor type), call its invoke(this) method directly.

The logic of this method implements the process of calling the interceptor chain and matching dynamic methods, ensuring that the corresponding logic can be executed before and after the method is called.

@Override
@Nullable
public Object proceed() throws Throwable {<!-- -->
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {<!-- -->
return invokeJoinpoint();
}

Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get( + + this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {<!-- -->
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {<!-- -->
return dm.interceptor.invoke(this);
}
else {<!-- -->
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {<!-- -->
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

AspectJAroundAdvice

The invoke method of the AspectJAroundAdvice class is a method used to perform around advice. Surround advice is a type of advice in AOP that can execute specific logic before and after the target method is called.

The following is an analysis of the AspectJAroundAdvice‘s invoke method:

@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
    // Check whether the incoming MethodInvocation is an instance of Spring's ProxyMethodInvocation
    if (!(mi instanceof ProxyMethodInvocation)) {<!-- -->
        // If not, throw IllegalStateException
        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
    }
    
    // Force the incoming MethodInvocation to the ProxyMethodInvocation type
    ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
    
    // Get the ProceedingJoinPoint object, which is the wraparound advice-specific interface in Spring AOP
    ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
    
    // Get the JoinPointMatch object for matching cut points
    JoinPointMatch jpm = getJoinPointMatch(pmi);
    
    //Call the invokeAdviceMethod method to execute the logic of surrounding notifications
    //The parameters are ProceedingJoinPoint, JoinPointMatch and null, null
    return invokeAdviceMethod(pjp, jpm, null, null);
}

analyze:

  1. Type checking: First, the method checks whether the incoming MethodInvocation object is an instance of ProxyMethodInvocation. If not, it means that the object passed in is not the instance of the Spring proxy method call, and an IllegalStateException exception is thrown.

  2. Type conversion: If the MethodInvocation object is an instance of ProxyMethodInvocation, it will be forced to the ProxyMethodInvocation type for subsequent Prepare for the operation.

  3. Get ProceedingJoinPoint and JoinPointMatch: Get the ProceedingJoinPoint object and JoinPointMatch object through the pmi object. ProceedingJoinPoint is an interface specific to surrounding advice in Spring AOP, which contains information about the target method. The JoinPointMatch object is used to match cut points.

  4. Invoke surround notification logic: Finally, call the invokeAdviceMethod method to execute the surround notification logic. The invokeAdviceMethod method may contain a specific implementation of surround advice, which accepts ProceedingJoinPoint, JoinPointMatch and additional parameters, then executes the notification logic and returns the result.

In general, the purpose of this invoke method is to convert the proxy method call in Spring AOP into the ProceedingJoinPoint object in AspectJ and execute the corresponding surrounding notification logic.

MethodBeforeAdviceInterceptor

The invoke method of MethodBeforeAdviceInterceptor is the key part for executing “before advice”. Pre-advice is a type of advice in AOP that executes specific logic before the target method is executed. The following is an analysis of this method:

public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
    //Call the before method of the notification before the target method is executed
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    
    // Continue executing the target method
    return mi.proceed();
}

analyze:

  1. Call the before method of pre-advice: First, before the target method is executed, pass this.advice.before(mi.getMethod(), mi.getArguments( ), mi.getThis()) calls the before method of the pre-advice (the implementation class of the MethodBeforeAdvice interface). This method is usually used to perform pre-logic, such as logging, permission checking, etc. The before method accepts the target method (mi.getMethod()), method parameters (mi.getArguments()) and the target object ( mi.getThis()) as parameter.

  2. Continue executing the target method: Afterwards, continue executing the target method through the mi.proceed() method. This method call will cause the program flow to enter the actual target method of the proxy.

The logic of this invoke method is very simple, but very important. It ensures that the pre-notification is triggered before the target method is executed, and then allows the target method to continue executing. This mechanism allows developers to insert custom logic before method execution without modifying the target method.

AspectJAfterAdvice

The invoke method of AspectJAfterAdvice is the key part for executing “after advice” (after advice). Post-advice is a type of advice in AOP that executes specific logic after the target method is executed. The following is an analysis of this method:

public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
    try {<!-- -->
        // Continue executing the target method
        return mi.proceed();
    } finally {<!-- -->
        // After the target method is executed, execute the post notification logic through invokeAdviceMethod
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}

analyze:

  1. Continue executing the target method: First, use the mi.proceed() method to continue executing the target method. This method call will cause the program flow to enter the actual target method of the proxy.

  2. Execute post-advice logic: Use invokeAdviceMethod(getJoinPointMatch(), null, null) to execute post-advice logic in the finally block. The getJoinPointMatch() method is used to obtain the matching join point. Here, AspectJAfterAdvice may use the connection point information to perform post logic. The finally block guarantees that the post-notification logic will be executed regardless of whether the target method throws an exception.

The logic of this invoke method is very clear: it ensures that the pre- and post-logic are executed before and after the target method is executed. In this method, the logic of the post-notification is placed in the finally block to ensure that the post-notification will be executed regardless of whether an exception occurs after the target method is executed. In this way, developers can insert custom logic after the method is executed without modifying the code of the target method.

AfterReturningAdviceInterceptor

The invoke method of AfterReturningAdviceInterceptor is the key part for executing “after returning advice” (after returning advice). Post-return notification is a type of notification in AOP that executes specific logic after the target method executes normally and returns. The following is an analysis of this method:

public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
    // Call the target method and get its return value
    Object returnValue = mi.proceed();
    
    // After the target method returns, call the afterReturning method of the notification
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    
    //Return the return value of the target method
    return returnValue;
}

analyze:

  1. Call the target method: First, use the mi.proceed() method to call the target method. This method call will cause the program flow to enter the actual target method of the proxy and obtain its return value.

  2. Execute post-return notification logic: After the target method returns, use this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis())Call the afterReturning method of the notification. This method is usually used to perform post-return logic, such as logging, result processing, etc. The afterReturning method accepts the return value of the target method (retVal), the target method (mi.getMethod()), and the method parameters (mi .getArguments()) and the target object (mi.getThis()) as parameters.

  3. Return the return value of the target method: Finally, return the return value of the target method to the caller.

The logic of this invoke method ensures that the custom post-return logic is executed after the target method returns normally. This mechanism allows developers to process or record the return value of the target method after it is executed.

AspectJAfterThrowingAdvice

The invoke method of AspectJAfterThrowingAdvice is the key part for performing “after throwing advice”. Post-exception notification is a type of notification in AOP that executes specific logic after the target method throws an exception. The following is an analysis of this method:

public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
    try {<!-- -->
        // Call target method
        return mi.proceed();
    } catch (Throwable ex) {<!-- -->
        //Catch the exception thrown by the target method
        // If certain conditions are met, call the notify's invokeAdviceMethod method
        if (shouldInvokeOnThrowing(ex)) {<!-- -->
            invokeAdviceMethod(getJoinPointMatch(), null, ex);
        }
        //Rethrow the caught exception
        throw ex;
    }
}

analyze:

  1. Call the target method: First, use the mi.proceed() method to call the target method. This method call will cause the program flow to enter the actual target method of the proxy.

  2. Catch exceptions: Use the try-catch block to catch exceptions thrown by the target method (Throwable ex).

  3. Determine whether to call the notification: Inside the catch block, use the shouldInvokeOnThrowing(ex) method to determine whether specific conditions are met. If so, call the notification’s invokeAdviceMethod method. This method may contain specific logic for notification after an exception is thrown. If the condition is not met, the notification logic is not executed.

  4. RethrowException: Finally, the caught exception will be rethrown regardless of whether the notification was called or not. This is done to keep the exception propagated so that the upper caller can handle the exception or continue to propagate the exception.

In general, the logic of this invoke method ensures that after the target method throws an exception, the corresponding notification logic is executed according to specific conditions and the propagation of the exception is maintained. This mechanism allows developers to execute custom logic when the target method throws an exception.

ThamNotVeryUsefulAspect

QhyuAspectService cooperates with ThamNotVeryUsefulAspect to view the execution order of all advice. Directly start the main method of QhyuApplication.

tham around before
tham Before method call
execute my method
tham afterReturning
tham After before method call
tham around after

The figure below organizes the entire calling logic.

Then I created a NotVeryUsefulAspect, and @Order let it execute first to see the overall execution process.

@Component
@Aspect
@Order(99)
public class NotVeryUsefulAspect {<!-- -->

@Pointcut("execution(* com.qhyu.cloud.aop.service.QhyuAspectService.*(..))") // the pointcut expression
private void anyOldTransfer() {<!-- -->} // the pointcut signature

@Before("anyOldTransfer()")
public void before(){<!-- -->
System.out.println("not Before method call");
}

@After("anyOldTransfer()")
public void after(){<!-- -->
System.out.println("not After before method call");
}

@AfterReturning("anyOldTransfer()")
public void afterReturning(){<!-- -->
System.out.println("not afterReturning");
}

@AfterThrowing("anyOldTransfer()")
public void afterThrowing(){<!-- -->
System.out.println("not AfterThrowing");
}

@Around("anyOldTransfer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{<!-- -->
// start stopwatch
System.out.println("not around before");
Object retVal = pjp.proceed();
// stop stopwatch
System.out.println("not around after");
return retVal;
}
}
not around before
not Before before the method is called
tham around before
tham Before method call
execute my method
tham afterReturning
tham After before method call
tham around after
not afterReturning
not After before method call
not around after

Note:

The @AfterReturning and @AfterThrowing notifications are indeed mutually exclusive, and only one of them will be executed after the target method completes execution. It depends on the execution result of the target method:

  • @AfterReturning notification: will be executed when the target method returns successfully, even if the return value of the target method is null.

  • @AfterThrowing Notification: Will be executed when the target method throws an exception.

If the target method returns successfully, the @AfterReturning notification will be executed; if the target method throws an exception, the @AfterThrowing notification will be executed. There will be no simultaneous execution between the two, only one of them will be triggered based on the result of the target method.