Preface (can be skipped)

Before we get started, let’s talk about something else. The last chapter of the Spring AOP Implementation Series was originally planned to cover Spring AOP source code. I was confident about it until I read the source code and realized it wasn’t that simple.

First of all, Spring AOP has a bit too much source code and is not concise enough, which makes writing a lot of trouble. Second, it doesn’t seem to make much sense to explain its implementation entirely based on Spring AOP source code.

So I decided to take a different approach, starting with the features and capabilities of Spring AOP, and then implementing a rough Spring AOP framework with Spring AOP implementation ideas.

Special note: in the process of implementation, due to the length, cut a lot of optimization, especially about factories, lazy loading, caching, concurrency, etc.

Function of split

From the previous chapter, we learned about the features of Spring AOP and what it can do. Let’s extract some of the highlights:

  • Note and XML configuration are supported
  • Can be integrated with Spring IoC
  • Supports JDK Dynamic Proxy and CGLIB Proxy
  • Integrate With AspectJ’s annotations, including aspects (@aspect), pointcuts (@pointcut), Advice (@advice), and so on
  • Implement calls to Advice in a dynamic proxy

We do a functional analysis for these features, which roughly have the following functions:

  • AOP, which focuses on the implementation of AOP functionality based on methods, is divided into three aspects:

    • Integrating AspectJ, including integrating expressions, using Annotations defined by AspectJ, and using tools provided by AspectJ;
    • The abstraction layer, which includes Spring’s encapsulation and abstraction of AOP concepts, focuses on advisors, which are unique to Spring.
    • Implementation layer, custom MethodInterceptor implements calls to AOP target methods and Advice methods
  • Object proxy: Supports JDK and CGLIB proxy methods to generate corresponding proxy objects based on target objects

  • Configuration parsing supports XML and Annotation configuration parsing, and implements corresponding Advisor parsing according to configuration

  • IoC integration, integration of BeanFactory, to achieve access to beans in IoC container; Implement BeanPostProcesser to incorporate configuration resolution, proxy object creation, and so on into the bean initialization process

Note: Let’s take a look at the official website to explain the Advisor: *

An advisor is like a small self-contained aspect that has a single piece of advice.

* Advisors are composite classes that have a single Advice and can use Pointcut, and can be considered a special Aspect.

After listing the Spring AOP capabilities, let’s discuss the process of implementing the capabilities

Resolution of the process

I simply divide the process into two parts, the proxy creation phase triggered by Spring IoC initialization and the proxy invocation phase triggered by program invocation. See the following figure for details of the process:

Agent Creation phase

  1. The Spring IoC container triggers Bean initialization through the BeanPostProcesser interface

  2. Call BeanPostProcesser postProcessAfterInitialization interface implementation method

  3. Enter the process of building the Advisor and find all matching advisors through reflection

  4. Filter out matching advisors

  5. Enter the process of creating the proxy, pass the collection of Advisors from the previous process to the proxy object, and determine which proxy method to use based on the rules

Proxy invocation phase

  1. External method calls. Using the log print aspect as an example, when a method in the log aspect is called, the invocation of the proxy is triggered
  2. By invoking a callback method, you delegate the target method to the callback method. When generating proxy objects, there are corresponding callbacks, such as CallBack in CGLIB and InvocationHandler in JDK
  3. In the callback method, build the method interceptor chain (more on interceptor chains later)
  4. The call is further delegated to the interceptor chain, which does the execution

implementation

After listing the Spring AOP capabilities, let’s move on to the implementation of some of the capabilities

AOP abstract concept definition

First, we abstract the concepts in AspectJ. We simply define the Aspect, JoinPoint, Advice, Pointcut, and other classes.

public class Aspect {}public class JoinPoint {}public interface Pointcut {
    public String getExpression(a);
}
public class Advice {
    private Method adviceMethod;
    private Aspect aspect;
}
Copy the code

SpringAOP introduces the concept of Advisor, and we also define an Advisor class

public class Advisor {
    private Advice advice;
    private Pointcut pointcut;
    private Aspect aspect;
}
Copy the code

To incorporate AspectJ expressions, we took the Pointcut a step further

Add string expression conversion to AspectJ expression (PointcutExpression)

	import org.aspectj.weaver.tools.PointcutExpression;
	//....

	/** * convert to AspectJ pointcut expression *@return* /
    public PointcutExpression buildPointcutExpression(a);
Copy the code

AspectJ parser class (PointcutParser) is introduced to implement buildPointcutExpression method

import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;

public class AspectJPointcut implements Pointcut {

    public String expression;

    public AspectJPointcut(String expression) {
        this.expression = expression;
    }

    @Override
    public String getExpression(a) {
        return this.expression;
    }
	
    
    @Override
    public PointcutExpression buildPointcutExpression(a) {
        PointcutParser parser = PointcutParser
      .getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
        return parser.parsePointcutExpression(this.expression); }}Copy the code

Above, several basic classes are defined. Some people say, why don’t you see BeforeAdvice definitions? I’ll keep that in mind until I introduce method interceptors later.

Agent Creation phase

The IoC integration

The integration itself is relatively simple, implement interface BeanPostProcessor and BeanFactoryAware, directly on the code

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;

public abstract class AbstractAOPProxyCreator implements BeanPostProcessor.BeanFactoryAware {
    
    // Subclasses can be implemented
    protected void initBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
    
    // Get the matching Advisor
    protected abstract List<Advisor> getMatchedAdvisors(a);

    // Create a proxy object
    protected abstract Object createProxy(List<Advisor> advisors, Object bean);

    @Override
    public void setBeanFactory(BeanFactory arg0) throws BeansException {}@Override
    public Object postProcessBeforeInitialization(Object bean, String beanName){
		return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName){
		returnbean; }}Copy the code

The setBeanFactory method that implements the BeanFactoryAware interface, As well as the BeanPostProcessor interface postProcessAfterInitialization method and postProcessBeforeInitialization method.

Let’s introduce the template method design pattern to formulate the processing flow:

	public Object postProcessAfterInitialization(Object bean, String beanName){
        // Build all advisors
        List<Advisor> advisors = buildAdvisors();
        // Get the matching Advisor
        advisors = this.getMatchedAdvisors();
        // Generate proxy objects based on the obtained Advisor
        Object object = createProxy(advisors,bean);
        // Return the proxy object
		return object == null ? bean : object;
    }
Copy the code

Parsing configuration

Parsing the configuration mainly involves using reflection, finding the classes marked by @aspect, then finding @advice, @pointcut, etc., and finally combining these into Advisor instances. The implementation is not complicated and will not be described again

public class AnnotationParser implements ConfigParser {
	// Avoid repeated builds and increase caching
    private final Map<String, List<Advisor>> cache = new ConcurrentHashMap<>();

    @Override
    public List<Advisor> parse(a) {
        if(cache ! =null) {
            return getAdvisorsFromCache();
        } 
		// Get all classes annotated by @aspect
        List<Class> allClasses = getAllAspectClasses();
        for (Class class1 : allClasses) {
            cache.putIfAbsent(class1.getName(), getAdvisorsByAspect(class1));
        }
        
        return getAdvisorsFromCache();
    }
    
    /** * Generates the Advisor class * from the Aspect class@param class1
     * @return* /
    private List<Advisor> getAdvisorsByAspect(Class class1) {
        List<Advisor> advisors = new ArrayList<>();
        for (Method method : getAdvisorMethods(class1)) {
            Advisor advisor = getAdvisor(method, class1.newInstance());
            advisors.add(advisor);
        }
        returnadvisors; }}Copy the code

Filter compliant advisors

We filter the proxy target Bean from all advisors using Bean methods and Advisor pointcuts using AspectJ expressions and alignment tools

	import org.aspectj.weaver.tools.ShadowMatch;
	
	/** * get the matching * from all advisors@param advisors
     * @return* /
    public static List<Advisor> getMatchedAdvisors(Class cls, List<Advisor> advisors) {
        List<Advisor> aList = new ArrayList<>();
        for (Method method : cls.getDeclaredMethods()) {
            for (Advisor advisor : advisors) {
                ShadowMatch match = advisor.getPointcut()
                                    .buildPointcutExpression()
                                    .matchesMethodExecution(method);
                if(match.alwaysMatches()) { aList.add(advisor); }}}return aList;
    }
Copy the code

Creating a proxy object

We define a factory to which the proxy object is handed over to be created

public class AOPProxyFactory {

    public Object getProxyObject(List<Advisor> advisors, Object bean) {
        if(isInterface()) {
           return new CglibProxyImpl(advisors,bean).getProxyObject();
        } else {
            return newJdkDynamicProxyImpl(advisors,bean).getProxyObject(); }}private boolean isInterface(a) {
        return false; }}Copy the code

There are also two Proxy methods implemented, JDK Dynamic Proxy and CGLIB, which are explained one by one below

Note: You can ignore the method interceptor chain for now

JDK implementation, need to implement the InvocationHandler interface, and in the interface method invoke method interceptor chain call

public class JdkDynamicProxyImpl extends AOPProxy implements InvocationHandler {

    public JdkDynamicProxyImpl(List<Advisor> advisors, Object bean) {
        super(advisors, bean);
    }

    @Override
    protected Object getProxyObject(a) {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), ReflectHelper.getInterfaces(this.getTarget().getClass()), this);
    }


    /** * implements the interface method of InvocationHandler, delegating the call to the interceptor chain */
	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        MyMethodInterceptor[] iterceptors = 
            AdvisorHelper.getMethodInterceptors(this.getAdvisors(), method);
        
        Object obj = new MethodInterceptorChain(iterceptors)
                        .intercept(method,args,proxy);
        returnobj; }}Copy the code

CGLIB is implemented through callbacks, which require CGLIB’s MethodInterceptor

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxyImpl extends AOPProxy {

    public CglibProxyImpl(List<Advisor> advisors, Object bean) {
        super(advisors, bean);
    }

    @Override
    protected Object getProxyObject(a) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.getTarget().getClass());
        enhancer.setCallback(new AOPInterceptor());
        return enhancer.create();
    }

    /** * Implements cglib interceptors that delegate interceptor calls to the interceptor chain */ in the Intercept
    private class AOPInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            MyMethodInterceptor[] iterceptors = AdvisorHelper.getMethodInterceptors(CglibProxyImpl.this.getAdvisors(), method);
            
            Object o = new MethodInterceptorChain(iterceptors)
                .intercept(method, args, obj);
            returno; }}}Copy the code

Now that we’ve basically implemented the process of creating proxy objects, let’s consider a question

How do we implement calling our defined Advice when we invoke the proxy method?

Consider: implementation of Advice method calls

Simple implementation example

Let’s start with a simple implementation, using the JDK proxy as an example:

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return obj;
    }
Copy the code

We know that any method that executes a proxy object goes to Invoke (refer back to chapter 3 on the implementation of dynamic proxies for those of you who are not familiar with dynamic proxies), so once inside Invoke we need to make the following decisions:

  1. Find all advisors associated with the proxy (this is not difficult, the proxy class has advisors)
  2. The collection of advisors is iterated to determine whether they match one by one. The rules are Method and Pointcut (you can also use the AspectJ utility class here)
  3. Find the matching Advisor, further find the Advice, and then execute the Advice

Let’s use BeforeAdvice as an example to show how to implement Advice calls

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BeforeAdvice beforeAdvice = getBeforeAdvice(method);
		beforeAdvice.before(proxy, method, args);// Call Advice
        return method.invoke(this.bean, args);
    }
	
	
	/** * Take returning BeforeAdvice as an example *@return* /
    private BeforeAdvice getBeforeAdvice(Method method) {
        for (Advisor advisor : this.getAdvisors()) {
            if(AdvisorHelper.isMatch(advisor, method) 
               && advisor.getAdvice() instanceof BeforeAdvice) {
                return(BeforeAdvice) advisor.getAdvice(); }}return null;
    }
Copy the code

So, what if we get matching Advice and AfterAdvice? We add code to the Invoke method

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BeforeAdvice beforeAdvice = getBeforeAdvice(method);
		beforeAdvice.before(proxy, method, args);// Call Advice
        
        Object o = method.invoke(this.bean, args);
        
        AfterAdvice afterAdvice = getAfterAdvice(method);
		afterAdvice.after(proxy, method, args);// Call Advice
        return o;
    }
Copy the code

So what if we get two or more of the same type of Advice? And there is an execution order requirement between Advice of the same type. This simple implementation does not suffice, and we need to introduce a chain of method interceptors

Chain of responsibility design patterns

As a simple extension, many interceptors and filters are implemented based on the responsibility chain pattern. Before defining the method interceptor chain, let’s take a look at how Tomcat implements filters.

Note: to be exact, Tomcat is implemented based on the JavaEE standard

Java servlet interface

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;

}

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    public void destroy(a);
}
Copy the code

Tomcat filter chain implementation

public final class ApplicationFilterChain implements FilterChain {
    
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
    
    void addFilter(ApplicationFilterConfig filterConfig) {
        / /..
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        // Call the next filter if there is one
        //C-1
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
			filter.doFilter(request, response, this);
            return;
        }

        // We fell off the end of the chain -- call the servlet instanceservlet.service(request, response); }}Copy the code

Session initializes the filter

public class SessionInitializerFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ((HttpServletRequest)request).getSession();
        //C-2
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // NO-OP
    }

    @Override
    public void destroy(a) {
        // NO-OP}}Copy the code

The above implementation has several key points:

  • ApplicationFilterChain controls the execution of the chain of responsibilities, which can also be used to sort elements or match rules, as shown in section C-1
  • ApplicationFilterChain implements chain calls through recursive calls
  • Each Filter can be registered/added to the chain of execution and passed to ApplicationFilterChain (chain-.dofilter) after it has executed itself.

Implement the method interceptor chain

We introduce the chain of responsibilities pattern, abstracting Advice into a MethodInterceptor. This has the following benefits for feature implementation:

  • Advice can be added dynamically
  • You can sort for Advice, rule matching, and so on
  • Individual Advice can implement its own business logic individually (such as BeforeAdvice), and it is easy to extend new Advice later

Define an interceptor. To distinguish it from CGLIB’s interceptor, we’ll call it MyMethodInterceptor

public interface MyMethodInterceptor {

    public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain);
    
}
Copy the code

Define BeforeAdvice and AfterAdvice

public class BeforeAdvice extends Advice implements MyMethodInterceptor {
    public BeforeAdvice(Method adviceMethod, Aspect aspect) {
        super(adviceMethod, aspect);
    }

    public void before(final Object target, final Method method, final Object[] args) {
        this.invokeAspectMethod(target, method, args);
        ;
    }

    @Override
    public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain) {
        this.before(target, method, arguments);
        returnchain.intercept(method, arguments, target); }}public class AfterAdvice extends Advice implements MyMethodInterceptor {
    
    public AfterAdvice(Method adviceMethod, Aspect aspect) {
        super(adviceMethod, aspect);
    }

    public void after(final Object target, final Method method, final Object[] args) {
        this.invokeAspectMethod(target, method, args);
    }

    @Override
    public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain) {
        Object obj = chain.intercept(method, arguments, target);
        this.after(target, method, arguments);
        returnobj; }}Copy the code

Implement the method interceptor chain

public class MethodInterceptorChain {

    private MyMethodInterceptor[] methodInterceptors;

    public MethodInterceptorChain(MyMethodInterceptor[] methodInterceptors) {
        this.methodInterceptors = methodInterceptors;
    }

    private int index = 0;

    public Object intercept(Method method, Object[] arguments, Object target) {
        if (index == methodInterceptors.length) {
            // call method
            return method.invoke(target, arguments);
        } else {
            return methodInterceptors[index++]
                	.intercept(method, arguments, target, this);
        }
        return null; }}Copy the code

So, going back to our original thinking, we can replace our simple implementation with a MethodInterceptorChain as follows:

	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MyMethodInterceptor[] iterceptors = 
            AdvisorHelper.getMethodInterceptors(this.getAdvisors(), method);
        
        Object obj = new MethodInterceptorChain(iterceptors)
                        .intercept(method,args,proxy);
        return obj;
    }
Copy the code

In this way, we move the invocation of the proxy method to the MethodInterceptorChain

Finally, there is a small hidden problem that the Advisor in the proxy object is all related to this class. We still need to find the interceptor that matches the method based on method and Pointcut. This is the same as the previous implementation of filtering Advisor, which is based on AspectJ

The last

After the method interceptor chain is covered, the flow of proxy invocation is clear and will not be described again.

Our implementation is only Annotation configuration based on AspectJ. Spring AOP also supports Schema configuration. Time and space reasons, I will not do in-depth discussion.

In addition to the chain of responsibility pattern highlighted in this article, Spring AOP employs a number of factory patterns, template method patterns, adapter patterns, and more. Especially when a large number of factories (such as Aspect factory, Advisor factory, etc.) are used in conjunction with Spring IoC, it can support powerful management of classes and objects (Aspect, Advisor, etc.), including loading strategies such as singleton, multi-instance, lazy loading, etc. The author thinks, these are worth everybody thorough study and research.

The appendix

Code address: github.com/wanghe9011/…