Detailed explanation of Spring AOP source code

Posted by forcerecon on Thu, 16 Dec 2021 15:43:35 +0100

The two powerful features of Spring are IOC and AOP. We know that the process of implementing AOP in Spring is processed in the Spring Bean post processor and executed during Bean initialization. Next, let's take a detailed look at how the source code does

All methods that generate proxy objects will be delegated to invocationhandler For proxy execution, let's first look at the implementation of the invoke method in JdkDynamicAopProxy

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;

        Class var8;
        try {
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                Boolean var18 = this.equals(args[0]);
                return var18;
            }

            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                Integer var17 = this.hashCode();
                return var17;
            }

            if (method.getDeclaringClass() != DecoratingProxy.class) {
                Object retVal;
                if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                    retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                    return retVal;
                }

                if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

                target = targetSource.getTarget();
                Class<?> targetClass = target != null ? target.getClass() : null;
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                if (chain.isEmpty()) {
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
                } else {
                    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                    retVal = invocation.proceed();
                }

                Class<?> returnType = method.getReturnType();
                if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                    retVal = proxy;
                } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                    throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
                }

                Object var12 = retVal;
                return var12;
            }

            var8 = AopProxyUtils.ultimateTargetClass(this.advised);
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }

            if (setProxyContext) {
                AopContext.setCurrentProxy(oldProxy);
            }

        }

        return var8;
    }

Let's just look at a few important methods

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

Spring uses the responsibility chain pattern to implement AOP, and the function of this method is to obtain the chain

We can understand that this chain is used to obtain the collection of methods used to enhance the target object (according to the execution order). If the chain we get is empty, it will directly return to the original section and return to the original object; if there is, it will trigger the execution of the interceptor chain (the chain).

                    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                    retVal = invocation.proceed();

Let's look at the processed method again

    @Nullable
    public Object proceed() throws Throwable {
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.invokeJoinpoint();
        } else {
            Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
                Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
                return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
            } else {
                return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
            }
        }
    }

We all know that there are actually five links of enhancement methods on the target method

They are: Post notification, return notification, exception notification, surround notification and pre notification

The order in which they trigger execution, that is, the order in which they are added, is

1,around

2,before

3,after

4,afterReturning

5,afterThrowing

Our processed method will first judge whether we have configured the methods of the corresponding stage in order. If so, execute the current enhanced method. If not, judge the next method again by recursive call; At the same time, a count will be maintained on the chain to manage the judged stage.

for instance

After we get the interceptor chain, we will first enter the around stage for judgment

1. We will first judge whether we have configured corresponding enhancement methods in the around stage. At this time, the chain count is increased by one, indicating that we are about to enter the next stage.

2. If there is an enhanced implementation of the method at around, the enhanced method is executed.

3. If not, recursively call the proceed method to proceed to the next stage of judgment

It should be noted here that we use recursive call to enter the method judgment, that is, after each stage of judgment, we will return to the interceptor chain, and the interceptor will tell us that we should enter that stage for judgment next time.

The purpose of returning to the interceptor chain here is because we do not necessarily configure the enhancement methods according to the established order in the configuration process. If the judgment is not specified by the interceptor chain, the phase sequence will be disordered.

Topics: Java Spring Back-end