Spring Source Exploration: Deeply Understanding the Design and Implementation of Spring AOP

Posted by cptnwinky on Tue, 04 Jun 2019 00:46:56 +0200

Write on the front: This article is intended for those who are familiar with Java object-oriented programming and have experience in using Spring open source frameworks.

AOP is often referred to as aspect-oriented programming. The main function of AOP is to extract the same code scattered in the business system and put it in one place for management. The advantage of this approach is that it reduces duplication of code writing and maintainability of the software. Why is it called Face-Oriented Programming? For example: Suppose there are many updates in our code that require administrator privileges to execute. If AOP is not used, permission validation is required in every function that starts with update, which results in a lot of duplicate code. At the same time, in case the demand changes one day, no longer restrict only administrators to execute these functions, then we have to remove the original code and this part of the code line by line, very troublesome. With the introduction of AOP, this work becomes simpler: we can put the code for privilege validation somewhere, and then implement the code for privilege validation before executing the function that starts with update in the system through certain configurations. So, in case the demand changes, we just need to change the code in one place. The functions that start with update are tangent points, which can be abstracted as a tangent horizontally, so AOP is called aspect-oriented programming.

The common application scenarios of AOP are logging, performance statistics, security authentication, transaction processing, exception handling, etc. We separate these codes from business logic codes. By separating these behaviors, we separate them into non-directed business logic codes, and then change these codes without affecting our business logic codes. And business logic codes are not aware of their existence, because business logic codes are "proxied".

The implementation of proxy mechanism in Spring AOP mainly contacts JDK dynamic proxy and CGLIB dynamic proxy. If you are not familiar with these two dynamic proxy mechanisms, you can refer to my blog before:
Deep understanding of JDK dynamic proxy mechanism
Deep Understanding of CGLIB Dynamic Agent Mechanism

Here is a review of some basic concepts in Spring AOP:

  • Connection Points: Functions where the target is enhanced;
  • Advice notification: Define what to do at connection points, provide weaving interfaces for aspect enhancement, such as BeforeAdvice, AfterAdvice, ThrowsAdvice, etc.
  • Pointcut Cut Pointcut: Pointcut (Cut Point) determines which join point Advice notification should act on. That is, Pointcut defines the set of methods that need to be enhanced. These sets can be selected according to certain rules, such as matching by a specific regular expression or method signature.
  • Advisor Notifier: Combines Aspect Enhanced Design (Advice) of the Target Method with Point cut. With Advisor, you can define which notification to use and which concerns to use it.
1,Advice

Advice is a basic interface in AOP, which is inherited by BeforeAdvice, AfterAdvice, ThrowsAdvice, etc.


Figure 1.1 Advice inheritance diagram

Take BeforeAdvice as an example. Let's look at its class hierarchy


Figure 1.2 Class Hierarchy of BeforeAdvice

In the inheritance relationship of BeforeAdvice, a pre-enhanced interface, MethodBeforeAdvice, defined as a class set for the target method to be enhanced, needs to implement a callback function before, as a callback function. When the implementation of the before method is configured in the Advice to the target method, it will be called back when the target method is called.


Figure 1.3 Method BeforeAdvice and callback function before

The call parameters of the callback function before are: Method object, which is the reflection object of the target method; Object [] object array, which contains the input parameters of the target method.

Similarly, there are similar callback functions in AfterReturning Advice under the AfterAdvice inheritance system.


Figure 1.4 AfterReturning Advice and its callback function afterReturn.png
2. Pointcut tangent point

As you can see from the basic interface definition of Pointcut, you need to return a Method Matcher. For the matching judgment function of Point, it is specifically accomplished by this MethodMatcher, that is to say, the MethodMatcher decides whether the current method call needs to be enhanced or whether the configured Advice notification for the current method call needs to be applied.



Figure 2.1 Basic interface definition for Pointcut

In the MethodMatcher interface, there is a matcher method, which plays an important role in matching connection points.

In the class inheritance system of Pointcut, the MethodMatcher object can be configured as JdkRegexpMethodPointcut and NameMatchMethodPointcut to complete the method matching judgment. In JdkRegexpMethodPointcut, we can see a matches method, which is the interface method defined by MethodMatcher. In the implementation of JdkRegexpMethodPointcut, this matches method uses regular expressions to match method names.


Figure 2.2 matches function in JdkRegexpMethodPointcut

In NameRegexpMethodPointcut, another implementation of matches method is given -- matching according to the fully qualified name of the method


Figure 2.3 Implementation of matches function in NameRegexpMethodPointcut

From Figure 2.4 and Figure 2.5, we can see that the invoke method of JdkDynamicAopProxy starts the call to matches method. This invoke method is the entry method of the Proxy object for proxy callbacks.



Figure 2.4 Call chain of matches method in NameMatchMethodPointcut

Figure 2.5 Call chain of matches method in JdkRegexpMethodPointcut
3. Advisor Notifier

In Spring AOP, we take the implementation of a Default Point cut Advisor as an example to understand the working principle of Advisor.

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(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() + "]";
    }

}

In DefaultPointcut Advisor, there are two attributes, Advice and Pointcut. With these two attributes, we can configure Advice and Pointcut, respectively. In DefaultPointcut Advisor, pointcut is set to Pointcut.True by default, which is defined in the Pointcut interface as

Pointcut TRUE = TruePointcut.INSTANCE;

TruePointcut.INSTANCE is a hungry singleton:


Figure 2.6 Implementation of TruePointcut

In the method Matcher implementation of TruePointcut, TrueMethod Matcher is used as the method matcher. This matcher requires the return of true results for any method matching, that is to say, for any method name matching, it will return the result of successful matching. Similarly, the implementation of TrueMethod Matcher is a singleton pattern.


Figure 2.7 Implementation of TrueMethod Matcher
4. Design and analysis of Spring AOP

As we mentioned earlier, in the process of using Spring AOP, we can achieve other operations before or after the execution of the target object through configuration. In fact, AOP completes a series of processes, establishes the proxy object for the target object, and then starts the interceptor of the proxy object to complete the injection process of various cross-sectional planes. This proxy object can be created by using JDK dynamic proxy or CGLIB dynamic proxy. At the same time, this series of weaving designs are implemented through a series of adapters. Through a series of Adapter designs, AOP cross-sectional design and Proxy mode can be organically combined.

In the AOP module of Prong, the generation of proxy objects is mainly accomplished by configuring and calling ProxyFactoryBean. The generation process of main proxy objects is encapsulated in ProxyFactoryBean.


Figure 4.1 Class Inheritance of ProxyFactory

In ProxyFactoryBean, the generation of proxy objects is based on the getObject method as the entry point.

public Object getObject() throws BeansException {
        //Initialization Notifier Chain
        initializeAdvisorChain();
        //Distinguish singleton and prototype types to generate corresponding Proxy objects
        if (isSingleton()) {
            return getSingletonInstance();
        }
        else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }

Configuring the Advisor chain for Proxy proxy objects is accomplished in the initializeAdvisorChain method. During the initialization process, there is a flag bit advisorChain Initialized, which is used to indicate whether the notifier has been initialized. If it has been initialized, it will not be initialized here, but will return directly. Because ProxyFactoryBean implements the BeanFactoryAware interface, and in the process of initializing beans, a setBeanFactory callback is set to all beans that implement the interface, that is to say, the corresponding BeanFactory can be obtained through the generated beans, so we can easily get the notification through java this.beanFactory.getBean(name).

private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
        if (this.advisorChainInitialized) {
            return;
        }

        if (!ObjectUtils.isEmpty(this.interceptorNames)) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
                        "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
            }

            // Globals can't be last unless we specified a targetSource using the property...
            if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
                    this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
                throw new AopConfigException("Target required after globals");
            }

            // Materialize interceptor chain from bean names.
            for (String name : this.interceptorNames) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Configuring advisor or advice '" + name + "'");
                }

                if (name.endsWith(GLOBAL_SUFFIX)) {
                    if (!(this.beanFactory instanceof ListableBeanFactory)) {
                        throw new AopConfigException(
                                "Can only use global advisors or interceptors with a ListableBeanFactory");
                    }
                    addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
                            name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
                }

                else {
                    // If we get here, we need to add a named interceptor.
                    // We must check if it's a singleton or prototype.
                    Object advice;
                    if (this.singleton || this.beanFactory.isSingleton(name)) {
                        // Add the real Advisor/Advice to the chain.
                        advice = this.beanFactory.getBean(name);
                    }
                    else {
                        // It's a prototype Advice or Advisor: replace with a prototype.
                        // Avoid unnecessary creation of prototype bean just for advisor chain initialization.
                        advice = new PrototypePlaceholderAdvisor(name);
                    }
                    addAdvisorOnChainCreation(advice, name);
                }
            }
        }

        this.advisorChainInitialized = true;
    }

As shown in the getObject method, if it is a singleton object, the getSingletonInstance method is called to generate the singleton's proxy object, otherwise the newPrototype Instance method is called.


Figure 4.2 Generates a singleton proxy object

Figure 4.3 Generates proxy objects of prototype type type type

Figure 4.4 Both of the above methods get the proxy object by passing the AopProxy returned by createAopProxy into the getProxy method.

There are AopProxy-type objects, and Spring uses this AopProxy interface class to effectively separate the implementation of AOP proxy objects from the rest of the framework. AopProxy is an interface implemented by two subclasses, CglibAopProxy and JdkDynamicProxy. That is to say, for the implementation of the subclasses of these two AopProxy interfaces, Spring uses AopProxy objects from CGLIB and JDK respectively.

The generation of specific proxy object is accomplished by AopProxyFactory in the implementation of AdvisedSupport, the base class of ProxyFactoryBean. The proxy object is either generated from JDK or obtained by CGLIB. Because ProxyFactoryBean itself is a subclass of AdvisedSupport, it is convenient to obtain AopFactory in ProxyFactoryBean. As you can see in ProxyCreator Support, the specific AopProxy is generated through AopProxyFactory. As for what kind of object to generate, all the information is in AdvisedSupport, which is also the input parameter of the method to generate AopProxy, which is set here as this itself, because ProxyCreatorSupport itself is a subclass of AdvisedSupport.


Figure 4.5 ProxyCreator Support generates AopProxy objects

In ProxyCreator Support, we generate AopProxy objects by creating AopProxy. As you can see in the code above, an AopProxyFactory is used. This AopProxyFactory actually uses DefaultAopProxyFactory. As the creation factory object of AopProxyFactory, it was created in the base class ProxyCreator Support of ProxyFactoryBean. When creating the AopProxyFactory, it is set to DefaultAopProxyFactory.

During the generation of AopProxy proxy objects, we need to consider which generation method to use. If the target object is an interface class, the JDK dynamic proxy is used to generate the proxy object; otherwise, CGLIB is used to generate the proxy object of the target object. In order to meet the generation requirements of different types of proxy objects, DefaultAopProxyFactory, as the generation factory of AopProxy objects, can generate these two kinds of AopProxy objects according to different requirements.

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (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 is an interface class, use JDK to generate AopProxy
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // Otherwise, use CGLIB to generate AopProxy objects
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

Generating AopProxy Proxy Proxy Proxy Objects by JDK Dynamic Proxy

@Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       // Where JDK is invoked to generate Proxy
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

CGLIB dynamic proxy generates AopProxy proxy objects

@Override
    public Object getProxy() {
        return getProxy(null);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
        }
        // Get the target object configured in the IoC container from Advised
        try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class<?> proxySuperClass = rootClass;
            if (ClassUtils.isCglibProxyClass(rootClass)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

            // Validate the class, writing log messages as necessary.
            validateClassIfNecessary(proxySuperClass, classLoader);

            // Verify the interface settings of proxy objects
                       // Create and configure the Enhancer of CGLIB, which is the main operation class of CGLIB
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);

            // Generate the proxy class and create a proxy instance.
            return createProxyClassAndInstance(enhancer, callbacks);
        }
        catch (CodeGenerationException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (IllegalArgumentException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of class [" +
                    this.advised.getTargetClass() + "]: " +
                    "Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (Throwable ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }

After encapsulating the target object with the AopProxy object, the getObject method of the ProxyFactoryBean obtains an AopProxy proxy object instead of a normal Java object. At this point, the application will no longer call the method implementation of the target target configured in the ProxyFactoryBean, but will be part of the AOP implementation. Method calls to target objects are first intercepted by AopProxy proxy objects, and callback entries are intercepted in different ways of generating AopProxy proxy objects.

Topics: JDK Spring Programming Java