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:
-
this.currentInterceptorIndex
represents the index of the current interceptor. Initially, the index value is -1 and then increments early. -
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. -
If the current interceptor index is not the last one, get the next interceptor or interceptor notification (
interceptorOrInterceptionAdvice
). -
The next conditional statement checks the type of
interceptorOrInterceptionAdvice
. If it is of typeInterceptorAndDynamicMethodMatcher
, 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.
-
If
interceptorOrInterceptionAdvice
is an ordinary interceptor (MethodInterceptor
type), call itsinvoke(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:
-
Type checking: First, the method checks whether the incoming
MethodInvocation
object is an instance ofProxyMethodInvocation
. If not, it means that the object passed in is not the instance of the Spring proxy method call, and anIllegalStateException
exception is thrown. -
Type conversion: If the
MethodInvocation
object is an instance ofProxyMethodInvocation
, it will be forced to theProxyMethodInvocation
type for subsequent Prepare for the operation. -
Get ProceedingJoinPoint and JoinPointMatch: Get the
ProceedingJoinPoint
object andJoinPointMatch
object through thepmi
object.ProceedingJoinPoint
is an interface specific to surrounding advice in Spring AOP, which contains information about the target method. TheJoinPointMatch
object is used to match cut points. -
Invoke surround notification logic: Finally, call the
invokeAdviceMethod
method to execute the surround notification logic. TheinvokeAdviceMethod
method may contain a specific implementation of surround advice, which acceptsProceedingJoinPoint
,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:
-
Call the
before
method of pre-advice: First, before the target method is executed, passthis.advice.before(mi.getMethod(), mi.getArguments( ), mi.getThis())
calls thebefore
method of the pre-advice (the implementation class of theMethodBeforeAdvice
interface). This method is usually used to perform pre-logic, such as logging, permission checking, etc. Thebefore
method accepts the target method (mi.getMethod()
), method parameters (mi.getArguments()
) and the target object (mi.getThis()
) as parameter. -
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:
-
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. -
Execute post-advice logic: Use
invokeAdviceMethod(getJoinPointMatch(), null, null)
to execute post-advice logic in thefinally
block. ThegetJoinPointMatch()
method is used to obtain the matching join point. Here,AspectJAfterAdvice
may use the connection point information to perform post logic. Thefinally
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:
-
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. -
Execute post-return notification logic: After the target method returns, use
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis())
Call theafterReturning
method of the notification. This method is usually used to perform post-return logic, such as logging, result processing, etc. TheafterReturning
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. -
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:
-
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. -
Catch exceptions: Use the
try-catch
block to catch exceptions thrown by the target method (Throwable ex
). -
Determine whether to call the notification: Inside the
catch
block, use theshouldInvokeOnThrowing(ex)
method to determine whether specific conditions are met. If so, call the notification’sinvokeAdviceMethod
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. -
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 isnull
. -
@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.