Detailed analysis of Spring’s AOP source code

Foreword

This article will study the entire process of calling AOP dynamic proxy objects to explore how the notification method in the aspect, similar to pre-notification or post-notification, affects the target bean< /strong> target method is enhanced.

Note: This article is based on JDK dynamic proxy.

Text

1. AOP dynamic proxy object structure analysis

In the sample project in the previous article, you can see what the bean of IMyService obtained from the container looks like in the test program. The debugging diagram is shown below.

You can see that the obtained bean is actually the JDK dynamic proxy object of MyService, and InvocationHandler is JdkDynamicAopProxy , JdkDynamicAopProxy holds ProxyFactory, and ProxyFactory holds the target object and notification chain.

2. AOP dynamic proxy object call analysis

When calling the method of the dynamic proxy object, it will call the invoke() method of InvocationHandler, where InvocationHandler is JdkDynamicAopProxy , so start the analysis with the invoke() method of JdkDynamicAopProxy as the entry point.

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 notification will not be applied to the equals() method unless the equals() method is defined in the interface implemented by the target object
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined & amp; & amp; AopUtils.isHashCodeMethod(method)) {
            // The notification will not be applied to the hashCode() method unless the hashCode() method is defined in the interface implemented by the target object
            return hashCode();
        }
        else if (method. getDeclaringClass() == DecoratingProxy. class) {
            return AopProxyUtils. ultimateTargetClass(this.advised);
        }
        else if (!this.advised.opaque & amp; & amp; method.getDeclaringClass().isInterface() & amp; & amp;
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Do not apply advice to methods defined in the Advised interface or its parent interface
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

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

        // get target object
        target = targetSource. getTarget();
        // Get the Class object of the target object
        Class<?> targetClass = (target != null ? target. getClass() : null);

        // Assemble the notifications in the notification chain that can act on the current method into an interceptor chain
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        if (chain. isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            // If the interceptor chain is empty, then call the target object method directly
            // AopUtils.invokeJoinpointUsingReflection() will eventually call method.invoke(target, args)
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // Create method caller MethodInvocation, actually ReflectiveMethodInvocation
            // The parameters passed in when creating ReflectiveMethodInvocation are: proxy object, target object, target method, target method parameters, target object Class object, interceptor chain
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Call the proceed() method of the method caller to start the recursive process of calling each notification method and target method
            retVal = invocation. proceed();
        }

        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())) {
            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()) {
            targetSource. releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}
Copy Code

The above invoke() method mainly does two things:

  • The first thing is to build all notifications in the notification chain that can act on the current target method into an interceptor chain, and generate a method caller ReflectiveMethodInvocation based on the interceptor chain;
  • The second thing is to call the proceed() method of ReflectiveMethodInvocation, which will call the target method, and during the call, the interceptors in the interceptor chain will also Will be executed to achieve the effect of enhanced functions.

Let’s take a look at how the notification chain is built into an interceptor chain. this.advised is actually the ProxyFactory when generating a dynamic proxy object, and ProxyFactory Inherited from AdvisedSupport, the method to construct the notification chain into an interceptor chain is getInterceptorsAndDynamicInterceptionAdvice() of AdvisedSupport, as shown below.

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    // First get the interceptor chain from the cache according to the Method, and when there is no cache, it will be generated and cached
    List<Object> cached = this.methodCache.get(cacheKey);
    if (cached == null) {
        // actually call the getInterceptorsAndDynamicInterceptionAdvice() method of DefaultAdvisorChainFactory to generate the interceptor chain
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}
Copy Code

The interceptor chain corresponding to each target method will be cached after generation, so the interceptor chain will be fetched from the cache first, and getInterceptorsAndDynamicInterceptionAdvice() of DefaultAdvisorChainFactory will be called when there is no cache. /strong> method to generate the interceptor chain, the getInterceptorsAndDynamicInterceptionAdvice() method is implemented as follows.

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
        Advised config, Method method, @Nullable Class<?> targetClass) {

    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    // First get the notification chain from the ProxyFactory
    Advisor[] advisors = config. getAdvisors();
    List<Object> interceptorList = new ArrayList<>(advisors. length);
    Class<?> actualClass = (targetClass != null ? targetClass : method. getDeclaringClass());
    Boolean hasIntroductions = null;

    for (Advisor advisor : advisors) {
        // Because the Advisor interface has two subclass interfaces, namely PointcutAdvisor and IntroductionAdvisor
        if (advisor instanceof PointcutAdvisor) {
            // Process PointcutAdvisor, all examples use PointcutAdvisor
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                // Get the pointcut object, where the mm type is AspectJExpressionPointcut
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                boolean match;
                if (mm instanceof IntroductionAwareMethodMatcher) {
                    if (hasIntroductions == null) {
                        hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
                    }
                    // Determine whether the current Advisor can act on the target method
                    match = ((IntroductionAwareMethodMatcher) mm). matches(method, actualClass, hasIntroductions);
                }
                else {
                    // Determine whether the current Advisor can act on the target method
                    match = mm. matches(method, actualClass);
                }
                if (match) {
                    // If the current Advisor can act on the target method, then convert the current Advisor to MethodInterceptor, that is, the notification is converted to a method interceptor
                    MethodInterceptor[] interceptors = registry. getInterceptors(advisor);
                    if (mm.isRuntime()) {
                        for (MethodInterceptor interceptor : interceptors) {
                            // Encapsulate the method interceptor and pointcut object into InterceptorAndDynamicMethodMatcher and add it to the interceptor chain
                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }
                    else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }
        else if (advisor instance of IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                Interceptor[] interceptors = registry. getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        else {
            Interceptor[] interceptors = registry. getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }

    return interceptorList;
}
Copy Code

The process of converting an advice chain to an interceptor chain is outlined below.

  • First obtain the point-cutting object from Advisor, and judge whether the current Advisor can act on the target method according to the point-cutting object;
  • Encapsulate the Advisor that can act on the target method into a method interceptor MethodInterceptor, and add the interceptor chain.

MethodInterceptor is an interface that defines a method interceptor. The method interceptors corresponding to the pre-advice and post-advice used in the example are MethodBeforeAdviceInterceptor and AspectJAfterAdvice< /strong>, their relationship can be represented by the following class diagram.

After the interceptor chain is obtained, another method invoker ReflectiveMethodInvocation will be created in the invoke() method of JdkDynamicAopProxy, and its class diagram is as follows shown.

From the class diagram, we can know that ReflectiveMethodInvocation holds the proxy object, target object, target method, target method parameters, Class object of the target object, interceptor chain, and subsequent call notification The entry point of the method and the logic of calling the target method is the proceed() method of ReflectiveMethodInvocation.

When the AOP dynamic proxy object is invoked above, the first thing to do in the invoke() method of JdkDynamicAopProxy is to notify all The advice that can act on the current target method is constructed into an interceptor chain, and the method invoker ReflectiveMethodInvocation is generated based on the interceptor chain. Let’s analyze the second thing, which is to call the proceed() method of ReflectiveMethodInvocation. By calling the proceed() method, you can call the target method before and after Apply the notification enhancement to the target method. Let’s analyze the entire calling process. The proceed() method of ReflectiveMethodInvocation is implemented as follows.

// currentInterceptorIndex is used to indicate the interceptor that needs to be called currently
// The initial value is -1, and it will be incremented by 1 before each use
private int currentInterceptorIndex = -1;

public Object proceed() throws Throwable {
    // When the interceptors have traversed, call the target method
    if (this. currentInterceptorIndex == this. interceptorsAndDynamicMethodMatchers. size() - 1) {
        // Call the invokeJoinpoint() method to execute the target method
        // invokeJoinpoint() will call AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments), that is, execute the target method through reflection
        return invokeJoinpoint();
    }

    // Get the interceptor
    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());
        if (dm. methodMatcher. matches(this. method, targetClass, this. arguments)) {
            // Call the invoke() method of the interceptor corresponding to various notifications
            return dm. interceptor. invoke(this);
        }
        else {
            return proceed();
        }
    }
    else {
        // When the interceptor is ExposeInvocationInterceptor, it will be called here
        // The invoke() method of ExposeInvocationInterceptor will first save the method caller ReflectiveMethodInvocation for the current thread
        // Then recursively call the proceed() method of ReflectiveMethodInvocation
        return ((MethodInterceptor) interceptorOrInterceptionAdvice). invoke(this);
    }
}
Copy Code

In the proceed() method of ReflectiveMethodInvocation, the invoke() method of all interceptors will be called first, and finally the target method will be called. But in reality, there are some notifications that need to be executed after the target method is executed or when the target method throws an exception, so it can be speculated that calling the invoke() method of the interceptor does not mean that the interceptor corresponds to The notification logic will be executed, and the invoke() method of the interceptor will recursively call back ReflectiveMethodInvocation‘s proceed()< at some point in time. /strong> method.

For the convenience of understanding, the three interceptors ExposeInvocationInterceptor, MethodBeforeAdviceInterceptor and AspectJAfterAdvice used in this example are described below.

  • The invoke() method of ExposeInvocationInterceptor is shown below.
public Object invoke(MethodInvocation mi) throws Throwable {
    MethodInvocation oldInvocation = invocation. get();
    // Save the method caller for the current thread, that is, save ReflectiveMethodInvocation
    invocation.set(mi);
    try {
        // Recursively call the proceed() method of ReflectiveMethodInvocation to execute other interceptors or target methods
        return mi. proceed();
    }
    finally {
        invocation.set(oldInvocation);
    }
}
Copy Code

ExposeInvocationInterceptor just saves a reference to the method invoker for the current thread.

  • The invoke() method of MethodBeforeAdviceInterceptor is shown below.
public Object invoke(MethodInvocation mi) throws Throwable {
    // The logic of the pre-notification must be executed before the target method, so the logic of the pre-notification is executed first here
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    // Recursively call the proceed() method of ReflectiveMethodInvocation to execute other interceptors or target methods
    return mi. proceed();
}
Copy Code

As long as the invoke() method of MethodBeforeAdviceInterceptor is called, the logic of the corresponding pre-advice will be executed, which is consistent with the fact that pre-advice is executed before the execution of the target method. After the pre-notification logic is executed, it will call back the proceed() method of ReflectiveMethodInvocation to call other interceptors and target methods.

  • The invoke() method of AspectJAfterAdvice is shown below.
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        // Recursively call the proceed() method of ReflectiveMethodInvocation to execute other interceptors or target methods first
        return mi. proceed();
    }
    finally {
        // The logic of the post-notification should be executed after the target method is executed, so the execution of the post-notification is placed in finally
        // It also shows that even if an exception is thrown during the execution of the target method or other interceptors, the logic of the post-notification will also be executed
        invokeAdviceMethod(getJoinPointMatch(), null, null);
    }
}
Copy Code

In the invoke() method of AspectJAfterAdvice, the post-notification logic call is placed in finally, so the post-notification logic must be Wait until other advice and target methods execute before executing.

So here, the process of the method caller ReflectiveMethodInvocation calling the interceptor and the target method has formed a closed loop. With the help of the recursive call feature, both the target method and the interceptor will be called, although the call of the target method It will be after all interceptor calls, but the execution of the target method will be executed before the execution of some notifications (such as post-notification).

Finally, it needs to be explained that in the proceed() method of ReflectiveMethodInvocation, the currentInterceptorIndex field is used to identify which interceptor is currently called. The initial value is -1, add 1 before each use (ie + + currentInterceptorIndex), then the position of the interceptor in the collection will actually affect the invoke() of the interceptor strong> method call sequence, then through the above source code analysis, the impact of this call sequence can be summarized as follows.

  • The positions of the interceptors corresponding to different notifications in the collection will not affect the calling order of different notifications. For example, the execution of the pre-notification logic will definitely precede the execution of the post-notification logic;
  • The position of the interceptor corresponding to the same notification in the collection will affect the calling order of the same notification. For example, the index of the pre-advice 1 in the collection is smaller than the index of the pre-notification 2 in the collection, then the execution of the logic of the pre-advice 1 Will be preceded by pre-notification 2.

3. Timing diagram

AOP When the dynamic proxy object executes the method, the calling sequence diagram is as follows.

Summary

The dynamic proxy object of SpringAOP holds the notification chain and the target object, so when calling the dynamic proxy object method, it will first find the Advisor that can act on the target method from the notification chain >, and then encapsulate each qualified Advisor into MethodInvocation and add it to the collection. The collection of MethodInvocation is called the interceptor chain, and the interceptor is obtained After the chain, the method invoker MethodInvocation will be created based on the interceptor chain, and then the interceptor and the target method will be invoked through the proceed() method of MethodInvocation logic.

syntaxbug.com © 2021 All Rights Reserved.