Implementation of Spring AOP

Posted by Virtuali on Thu, 04 Nov 2021 23:05:20 +0100

Before understanding the implementation of Spring AOP, first understand some concepts related to Spring AOP

Related concepts of AOP

When using Spring for AOP related programming, we often use Advice, PointCut and Advisor to realize the functions we need.

Advice

Advice is an interface defined by the AOP alliance, which defines what we can do at the tangent point, that is, the enhancement logic we want to weave in, and provides an entry for slice enhancement. In spring, advice is a unified interface. Spring defines specific notification types based on advice, such as,

  • BeforeAdvice: pre enhanced interface, callback before the target method call.
  • AfterAdvice: a post enhanced interface that calls back when the target method call ends and returns successfully.
  • Throwadadvice: callback when an exception is thrown.
  • Interceptor: represents a general interceptor that can be enhanced before and after method calls.
  • DynamicIntroductionAdvice: unlike the above Advice and Interceptor, DynamicIntroductionAdvice does not enhance methods, but dynamically introduces new interface implementations. We can add an interface implementation for the target class (the original target class did not implement an interface), so we can create a proxy for the target class to implement an interface through dynamic introduction Advice enhancement.

Pointcut

Pointcut determines which connection points Advice can act on, that is, through pointcut, we can define a set of methods that need to be enhanced. The collection of these methods can be selected by the rules defined in pointcut. When the method meets the rules defined in pointcut, it returns true. These rules can be regular expressions, string matching, etc.

Spring defines the Pointcut interface, which defines the abstract methods used to obtain class filters and method matchers.

public interface Pointcut {

    /**
     * Return the ClassFilter for this pointcut.
     * @return the ClassFilter (never {@code null})
     */
    ClassFilter getClassFilter();

    /**
     * Return the MethodMatcher for this pointcut.
     * @return the MethodMatcher (never {@code null})
     */
    MethodMatcher getMethodMatcher();


    /**
     * Canonical Pointcut instance that always matches.
     */
    Pointcut TRUE = TruePointcut.INSTANCE;

}

However, with class filters and method matchers, we also need to know how to use class filters and method matchers. Therefore, we need to implement both Pointcut and MethodMatcher. MethodMatcher defines the matches method, that is, the method used for rule matching.

public interface MethodMatcher {

    /**
     * Perform static checking whether the given method matches.
     * <p>If this returns {@code false} or if the {@link #isRuntime()}
     * method returns {@code false}, no runtime check (i.e. no
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
     * will be made.
     * @param method the candidate method
     * @param targetClass the target class
     * @return whether or not this method matches statically
     */
    boolean matches(Method method, Class<?> targetClass);

    /**
     * Is this MethodMatcher dynamic, that is, must a final call be made on the
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
     * runtime even if the 2-arg matches method returns {@code true}?
     * <p>Can be invoked when an AOP proxy is created, and need not be invoked
     * again before each method invocation,
     * @return whether or not a runtime match via the 3-arg
     * {@link #matches(java.lang.reflect.Method, Class, Object[])} method
     * is required if static matching passed
     */
    boolean isRuntime();

    /**
     * Check whether there a runtime (dynamic) match for this method,
     * which must have matched statically.
     * <p>This method is invoked only if the 2-arg matches method returns
     * {@code true} for the given method and target class, and if the
     * {@link #isRuntime()} method returns {@code true}. Invoked
     * immediately before potential running of the advice, after any
     * advice earlier in the advice chain has run.
     * @param method the candidate method
     * @param targetClass the target class
     * @param args arguments to the method
     * @return whether there's a runtime match
     * @see MethodMatcher#matches(Method, Class)
     */
    boolean matches(Method method, Class<?> targetClass, Object... args);


    /**
     * Canonical instance that matches all methods.
     */
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

The following figure shows the inheritance relationship of some pointcuts in Spring. You can see that the specific implementations integrate the Pointcut interface and the MethodMatcher interface.

Advisor

The enhanced logic of Advice was introduced earlier. Pointcut defines the set of methods, but which methods use which enhanced logic is still not related. Advisor combines Advice and pointcut. Through advisor, you can define which Advice to use on a pointcut connection point.
Spring provides a DefaultPointcutAdvisor. In the DefaultPointcutAdvisor, there are two properties, Advice and pointcut, which are used to configure Advice and pointcut. Its implementation is as follows.

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;


    /**
     * Create an empty DefaultPointcutAdvisor.
     * <p>Advice must be set before use using setter methods.
     * Pointcut will normally be set also, but defaults to {@code Pointcut.TRUE}.
     */
    public DefaultPointcutAdvisor() {
    }

    /**
     * Create a DefaultPointcutAdvisor that matches all methods.
     * <p>{@code Pointcut.TRUE} will be used as Pointcut.
     * @param advice the Advice to use
     */
    public DefaultPointcutAdvisor(Advice advice) {
        this(Pointcut.TRUE, advice);
    }

    /**
     * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice.
     * @param pointcut the Pointcut targeting the Advice
     * @param advice the Advice to run when Pointcut matches
     */
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }


    /**
     * Specify the pointcut targeting the advice.
     * <p>Default is {@code Pointcut.TRUE}.
     * @see #setAdvice
     */
    public void setPointcut(@Nullable Pointcut pointcut) {
        this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }


    @Override
    public String toString() {
        return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }

}

Implementation of Spring AOP

The related concepts of Spring AOP have been introduced earlier, but how does Spring AOP intercept method calls? The following is an analysis of the implementation of Spring AOP.
Similarly, start the implementation analysis of Spring AOP with the unit test in Spring.
Start the implementation analysis of Spring AOP with the following code.

@Test
public void testProxyFactory() {
    TestBean target = new TestBean();
    ProxyFactory pf = new ProxyFactory(target);
    NopInterceptor nop = new NopInterceptor();
    CountingBeforeAdvice cba = new CountingBeforeAdvice();
    Advisor advisor = new DefaultPointcutAdvisor(cba);
    pf.addAdvice(nop);
    pf.addAdvisor(advisor);
    ITestBean proxied = (ITestBean) pf.getProxy();
    proxied.setAge(5);
    assertThat(cba.getCalls()).isEqualTo(1);
    assertThat(nop.getCount()).isEqualTo(1);
    assertThat(pf.removeAdvisor(advisor)).isTrue();
    assertThat(proxied.getAge()).isEqualTo(5);
    assertThat(cba.getCalls()).isEqualTo(1);
    assertThat(nop.getCount()).isEqualTo(2);
}

In the above code, create a TestBean, nopinterceptor and CountingBeforeAdvice object, initialize ProxyFactory with TestBean, initialize DefaultPointcutAdvisor with CountingBeforeAdvice object, and add nopinterceptor DefaultPointcutAdvisor to ProxyFactory. You can see that Pointcut is not specified in the above code, which means that the default Pointcut.TRUE is used, that is, all methods are enhanced.

First, let's take a look at the inheritance relationship of ProxyFactory.

From the top to the bottom, the first is TargetClassAware, which defines a getTargetClass() method to obtain the Class of the target object. The Advised interface inherits this interface. The Advised interface defines the method to obtain and set the AOP proxy factory configuration. The specific code is as follows:

public interface Advised extends TargetClassAware {

    boolean isFrozen();

    boolean isProxyTargetClass();

    Class<?>[] getProxiedInterfaces();

    boolean isInterfaceProxied(Class<?> intf);

    void setTargetSource(TargetSource targetSource);

    TargetSource getTargetSource();

    void setExposeProxy(boolean exposeProxy);

    boolean isExposeProxy();

    void setPreFiltered(boolean preFiltered);

    boolean isPreFiltered();

    Advisor[] getAdvisors();

    default int getAdvisorCount() {
        return getAdvisors().length;
    }

    void addAdvisor(Advisor advisor) throws AopConfigException;
    
    void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
    
    boolean removeAdvisor(Advisor advisor);
    
    void removeAdvisor(int index) throws AopConfigException;

    int indexOf(Advisor advisor);
    
    boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
    
    void addAdvice(Advice advice) throws AopConfigException;
    
    void addAdvice(int pos, Advice advice) throws AopConfigException;
    
    boolean removeAdvice(Advice advice);
    
    int indexOf(Advice advice);

    String toProxyConfigString();
}

ProxyConfig saves some properties of AOP agent factory and can be regarded as a data base class, as follows:

public class ProxyConfig implements Serializable {
    ...
    private boolean proxyTargetClass = false;

    private boolean optimize = false;

    boolean opaque = false;

    boolean exposeProxy = false;

    private boolean frozen = false;
    ...
}

AdvisedSupport inherits ProxyConfig, implements the Advised interface, and encapsulates AOP operations related to Advice and Advisor.

/** The AdvisorChainFactory to use. */
AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();

/** Cache with Method as key and advisor chain List as value. */
private transient Map<MethodCacheKey, List<Object>> methodCache;

/**
 * Interfaces to be implemented by the proxy. Held in List to keep the order
 * of registration, to create JDK proxy with specified order of interfaces.
 */
private List<Class<?>> interfaces = new ArrayList<>();

/**
 * List of Advisors. If an Advice is added, it will be wrapped
 * in an Advisor before being added to this List.
 */
private List<Advisor> advisors = new ArrayList<>();

Proxycreator support provides methods for setting ProxyFactory and creating proxy objects. The specific proxy objects created are left to the specific ProxyFactory.
The following are three specific implementations of ProxyFactory:

  • ProxyFactory, AOP can be configured declaratively in IOC container.
  • ProxyFactoryBean needs to use AOP programmatically
  • AspectProxyFactory integrates Spring and AspectJ for AOP applications using AspectJ.

After understanding the inheritance relationship of ProxyFactory, continue to look down. We already know that the creation of specific proxy objects is entrusted to specific ProxyFactory.
We mainly focus on the following line of code to obtain the proxy object:

ITestBean proxied = (ITestBean) pf.getProxy();

The implementation of getProxy of ProxyFactory is as follows:

public Object getProxy() {
    return createAopProxy().getProxy();
}

getProxy() calls createAopProxy() of proxycreator support to create AopProxy.

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}

createAopProxy() first obtains AopProxyFactory through getAopProxyFactory(). GetAopProxyFactory () returns a DefaultAopProxyFactory object directly, then calls the createAopProxy () method of DefaultAopProxyFactory to create the concrete AopProxy and introduces the this pointer, that is, the ProxyFactory object itself, because ProxyFactory inherits AdvisedSupport.

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!NativeDetector.inNativeImage() &&
            (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    Class<?>[] ifcs = config.getProxiedInterfaces();
    return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
  • Config. Iseptimize(): indicates whether the optimization policy is used, which is determined by the configured attribute optimize value;
  • config.isProxyTargetClass(): indicates whether it is a proxy target class, which is determined by the configured attribute proxy target class value;
  • hasNoUserSuppliedProxyInterfaces(): it is used to judge whether the proxy object has an implementation interface

When the proxy is an interface, use JdkDynamicAopProxy; otherwise, use ObjenesisCglibAopProxy().
JdkDynamicAopProxy saves the config and the interface that needs a proxy.

public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
    Assert.notNull(config, "AdvisedSupport must not be null");
    if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
        throw new AopConfigException("No advisors and no TargetSource specified");
    }
    this.advised = config;
    this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}

When the advised does not implement the SpringProxy, advised and decoratingproxy interfaces, AopProxyUtils.completeProxiedInterfaces() will add these three interfaces respectively.

Here, the AopProxyFactory is instantiated. Continue to see what getProxy() does.

public Object getProxy() {
    return getProxy(ClassUtils.getDefaultClassLoader());
}
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

The proxy object of the target object is created by passing classLoader, proxiedInterfaces and this into newProxyInstance. JdkDynamicAopProxy implements the InvocationHandler interface, so you can pass this pointer in to create a proxy object.

After the proxy object is created, when we call the method of the proxy object, we will call back the invoke() method of JdkDynamicAopProxy. Here, we only see the creation of proxy objects, but we still don't see the logic of how to enhance the method, because the implementation of code enhancement is in the invoke() method.

Next, look at the invoke() method.

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 && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        else if (!this.hashCodeDefined && 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 && method.getDeclaringClass().isInterface() &&
                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.
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !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 && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

The input parameters of invoke() are the proxy object, the method to be called, and the parameters of the method to be called. The invoke() party first checks whether the method is equal, hashCode method, declaringClass is decorating proxy, and whether the proxy needs to be set in the AopContext. After this series of checks, get the interceptor and Advice through getinterceptorsanddynamicinception Advice() and save them in the chain. If the chain is empty, it means there is no interceptor and Advice, then call the target method directly through the reflection method, invokeJoinpointUsingReflection() Method encapsulates the logic of the reflection call. If it is not empty, a ReflectiveMethodInvocation object is constructed. The processed method of the ReflectiveMethodInvocation object encapsulates the enhanced logic of the Advice method.

Let's take a look at the implementation of getinterceptorsanddynamicinception advice():

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    List<Object> cached = this.methodCache.get(cacheKey);
    if (cached == null) {
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}

First encapsulate the method into a MethodCacheKey, and then try to get the cache corresponding to this key from the cache. If not, get it through advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(). The default implementation of advisorChainFactory here is defaultadvisor chainfactory. Take a look at the implementation of getInterceptorsAndDynamicInterceptionAdvice().

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

    // This is somewhat tricky... We have to process introductions first,
    // but we need to preserve order in the ultimate list.
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    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) {
        if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                boolean match;
                if (mm instanceof IntroductionAwareMethodMatcher) {
                    if (hasIntroductions == null) {
                        hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
                    }
                    match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
                }
                else {
                    match = mm.matches(method, actualClass);
                }
                if (match) {
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    if (mm.isRuntime()) {
                        // Creating a new object instance in the getInterceptors() method
                        // isn't a problem as we normally cache created chains.
                        for (MethodInterceptor interceptor : interceptors) {
                            interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }
                    else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }
        else if (advisor instanceof 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;
}

The above code first obtains the instance of defaultadvisor AdapterRegistry through globaladvisor AdapterRegistry. Getinstance(). Defaultadvisor AdapterRegistry registers three kinds of advised adapters to adapt Advice to interceptors.

public DefaultAdvisorAdapterRegistry() {
    registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
    registerAdvisorAdapter(new AfterReturningAdviceAdapter());
    registerAdvisorAdapter(new ThrowsAdviceAdapter());
}

Then traverse the added advisors one by one. First, judge whether they are PointcutAdvisor, and then whether they are IntroductionAdvisor. If they are not, they are considered interceptors. If the current advisor is PointcutAdvisor, first judge whether it has been filtered in advance, or whether the class complies with the rules defined in ClassFilter. If you further judge the class of MethodMatcher Whether the type and method match. Whether PointcutAdvisor, IntroductionAdvisor or Interceptor, finally, the advisor is adapted through the registry.getInterceptors() method, and the advisor object is adapted into an instance of MethodInterceptor through the responding adapter. The specific implementation is as follows:

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<>(3);
    Advice advice = advisor.getAdvice();
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    return interceptors.toArray(new MethodInterceptor[0]);
}

Take a look at the implementation of one of the adapter s.

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
    }

    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }

}

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

    private final MethodBeforeAdvice advice;


    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }


    @Override
    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }

}

It can be seen that the BeforeAdvice is finally configured as a MethodBeforeAdviceInterceptor, and the MethodInterceptor interface is implemented. The invoke method is the entry of the interceptor chain.

Continue to look at the implementation of proceed.

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);
    }
}

The index is incremented from - 1. If all interceptors or Advice are called, the target function is called through reflection. If the current interceptorinterceptationadvice is an instance of InterceptorAndDynamicMethodMatcher, match it first through the matches method. If the match is successful, call the invoke method of interceptor. Otherwise, skip it. If it is not intercepto The instance of randdynamicmethodmatcher indicates that it is an interceptor and calls the invoke method directly.

In combination with the invoke method of MethodInterceptor above, we can see that all Advice and interceptors are strung into an interceptor chain. From the beginning, match through the matches method, and enhance if the matching is successful. Otherwise, continue to look down until the end and call the target method. The whole process is the enhancement process of the target method and the implementation principle of AOP.

summary

Taking ProxyFactory as an example, this paper analyzes the implementation of Spring AOP. Its implementation principle can be roughly divided into three parts:

  1. Implementation of advice, pointcut and advisor
  2. Generation of target object proxy object.
  3. The Advice is adapted and assembled into an interceptor chain, and the target method is enhanced through the interceptor chain.

Topics: Spring Back-end AOP