Original address: Sharp Bean’s blog

Last time we looked at the IoC implementation of Spring. You can check out the click link on my blog, and in this lecture we’ll continue talking about another important Spring feature, AOP. Most of the tutorials I have read before are not very thorough about the implementation of Spring Aop. Most of the articles describe the underlying technology of Spring Aop using dynamic proxies, and the implementation of Spring Aop is vague. After reading this kind of article, my mind is like this picture:

My idea is to walk you through the implementation of Spring Aop first, then block out the details and implement an Aop framework yourself. Deepen your understanding of Spring Aop. As you look at steps 1-4 above, add additional details between steps 4 and 5.

After reading this article, you will know:

  • What is Aop?
  • Why use Aop?
  • What is the idea behind Spirng implementing Aop
  • Implement an Aop framework yourself based on Spring ideas

What is Aop?

Aspect-oriented Programming (AOP). A technique for unified maintenance of program functions by means of precompilation and runtime dynamic proxies.

Why do you need Aop?

Section-oriented programming is actually a technique to uniformly add functionality to the original program without modifying the source code through precompilation or dynamic proxy techniques. Let’s look at a few keywords. The first one is “dynamic proxy technology”, which is the underlying technology of Spring Aop implementation. The second “no modification of source code”, and this is the key aspect of Aop, is what we normally call non-invasive. The third “add features”, do not change the original source code, for the program to add features.

For example, if one day you need to count the execution times of several methods, what you would do if you weren’t using Aop would be to get a start time for each method and an end time for each method. The difference is the execution time of the method. If you did this for every method that needed statistics, the code would be a disaster. If we use Aop techniques, we can add a slice of statistical method execution time without modifying the code. The code becomes very elegant. How does this section work? You will find out after reading the following article.

How is Spring Aop implemented?

The so-called:

Computer program = data structure + algorithm

After reading the Spring source code, you will understand this statement better.

The code for Spring Aop implementations is very, very convoluted. That is, Spring makes a very deep abstraction for flexibility. Spring also uses many Adapter patterns to further complicate the code for compatibility with the @AspectJ Aop protocol. Aop implementation of Spring consists of the following steps:

  1. Initialize the Aop container.
  2. Read the configuration file.
  3. Install the configuration file into a data structure that Aop recognizes —Advisor. Here we expand on the Advisor. The Advisor object contains two more important data structures, one isAdvice, one isPointcut.AdviceIs to describe the behavior of a slice,pointcutIt describes the position of the section. The combination of two data knots is “where, what”. suchAdvisorContains “where and what” information, and can fully describe the aspect.
  4. Spring converts this Advisor into a data structure that it can recognizeAdvicedSupport. Spirng dynamically weaves these method interceptors into corresponding methods.
  5. Generate dynamic proxy proxies.
  6. Provides calls that, when used, the caller invokes the proxy method. That is, methods that have been woven into enhancement methods.

Implement an Aop framework yourself.

Again, I referenced Aop design. Only method-based interceptors are implemented. Removed a lot of implementation details.

Manage objects using the IoC framework from the previous section. Use Cglib as the base class for dynamic proxies. Manage JAR packages and modules using Maven. So the IoC framework from last week will be introduced into the project as a Modules.

Let’s implement our Aop framework.

Let’s start with the basic structure of the code.

The code structure is much more complex than IoC. Let’s start with a brief overview of what each package does.

  • invocationIt describes a method call. Note that I am referring to “method call”, not calling the action.
  • interceptorThe most familiar interceptor, the interceptor intercepts the targetinvcationThe call inside the package.
  • advisorThe objects in this package are all data structures that describe facets.
  • adapterInside this package are some adapter methods. For those of you who are not familiar with adaptors, check out Adaptor Patterns in Design Patterns. His role is to bringadviceThe object in the bag fits intointerceptor.
  • beanThe object that describes our JSON configuration file.
  • coreThe core logic of our framework.

At this time, we have roughly sorted out a route from a macro perspective. Adaper ADAPTS the Advisor as an Interceptor to intercept invoction.

Let’s start at the very end of the chain:

invcation

First the MethodInvocation is the interface for all method calls. Calls to describe a method have three methods: get the method itself, getMethod, get the method’s argument, getArguments, and proceed(), the method itself.

public interface MethodInvocation {
    Method getMethod(a);
    Object[] getArguments();
    Object proceed(a) throws Throwable;
}
Copy the code

The ProxyMethodInvocation adds a method to get the proxy.

public interface ProxyMethodInvocation extends MethodInvocation {
    Object getProxy(a);
}
Copy the code

interceptor

AopMethodInterceptor is the interface that all interceptors of an Aop container implement:

public interface AopMethodInterceptor {
    Object invoke(MethodInvocation mi) throws Throwable;
}
Copy the code

At the same time we realized the two kinds of interceptor BeforeMethodAdviceInterceptor and AfterRunningAdviceInterceptor, just as its name implies is the former before method performs interception, the latter is the method after the interception:

public class BeforeMethodAdviceInterceptor implements AopMethodInterceptor {
    private BeforeMethodAdvice advice;
    public BeforeMethodAdviceInterceptor(BeforeMethodAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        advice.before(mi.getMethod(),mi.getArguments(),mi);
        returnmi.proceed(); }}Copy the code
public class AfterRunningAdviceInterceptor implements AopMethodInterceptor {
    private AfterRunningAdvice advice;

    public AfterRunningAdviceInterceptor(AfterRunningAdvice advice) {
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object returnVal = mi.proceed();
        advice.after(returnVal,mi.getMethod(),mi.getArguments(),mi);
        returnreturnVal; }}Copy the code

Looking at the code above, we see that mi.proceed() is actually the original method. Advice, as described above, is a data structure that describes the “do” of the enhanced method, so for the before interceptor, we put the advice corresponding enhancement method before the method that is actually executed. For the After interceptor, it comes after the method that actually executes.

This time we look at the most critical ReflectioveMethodeInvocation

public class ReflectioveMethodeInvocation implements ProxyMethodInvocation {
    public ReflectioveMethodeInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList) {
        this.proxy = proxy;
        this.target = target;
        this.method = method;
        this.arguments = arguments;
        this.interceptorList = interceptorList;
    }

    protected final Object proxy;

    protected final Object target;

    protected final Method method;

    protected Object[] arguments = new Object[0];

    // Store all interceptors
    protected final List<AopMethodInterceptor> interceptorList;

    private int currentInterceptorIndex = -1;

    @Override
    public Object getProxy(a) {
        return proxy;
    }

    @Override
    public Method getMethod(a) {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    @Override
    public Object proceed(a) throws Throwable {

        // Execute the target method after executing all interceptors
        if(currentInterceptorIndex == this.interceptorList.size() - 1) {
            return invokeOriginal();
        }

        // Iterative execution interceptor. Recalling the above, we implement a block that executes im.proceed() that actually calls this method again. A recursive call is implemented until all interceptors are executed.
        AopMethodInterceptor interceptor = interceptorList.get(++currentInterceptorIndex);
        return interceptor.invoke(this);

    }

    protected Object invokeOriginal(a) throws Throwable{
        returnReflectionUtils.invokeMethodUseReflection(target,method,arguments); }}Copy the code

In practice, our approach is likely to be enhanced by interceptors for multiple methods. So we use a list to hold all the interceptors. So we need to recursively add interceptors. When all interceptors are processed, the enhanced method is actually called. We can assume that this is where the dynamic weaving code described above occurs.

public class CglibMethodInvocation extends ReflectioveMethodeInvocation {

    private MethodProxy methodProxy;

    public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList, MethodProxy methodProxy) {
        super(proxy, target, method, arguments, interceptorList);
        this.methodProxy = methodProxy;
    }

    @Override
    protected Object invokeOriginal(a) throws Throwable {
        returnmethodProxy.invoke(target,arguments); }}Copy the code

CglibMethodInvocation just overrides the invokeOriginal method. Use proxy classes to invoke enhanced methods.

advisor

This package is full of data structures that describe facets, and we’ll cover two important ones.

@Data
public class Advisor {
    What to do / /
    private Advice advice;
    / / where is it
    private Pointcut pointcut;

}
Copy the code

As mentioned above, the Advisor describes where and what to do.

@Data
public class AdvisedSupport extends Advisor {
    // Target object
    private TargetSource targetSource;
    // List of interceptors
    private List<AopMethodInterceptor> list = new LinkedList<>();

    public void addAopMethodInterceptor(AopMethodInterceptor interceptor){
        list.add(interceptor);
    }

    public void addAopMethodInterceptors(List<AopMethodInterceptor> interceptors){ list.addAll(interceptors); }}Copy the code

This AdvisedSupport is a data structure that our Aop framework understands, and the question then becomes which interceptors to add to which target.

core

With that in mind, let’s move on to the core logic.

@Data
public class CglibAopProxy implements AopProxy{
    private AdvisedSupport advised;
    private Object[] constructorArgs;
    privateClass<? >[] constructorArgTypes;public CglibAopProxy(AdvisedSupport config){
        this.advised = config;
    }

    @Override
    public Object getProxy(a) {
        return getProxy(null);
    }
    @Override
    public Object getProxy(ClassLoader classLoader) { Class<? > rootClass = advised.getTargetSource().getTagetClass();if(classLoader == null){
            classLoader = ClassUtils.getDefultClassLoader();
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(rootClass.getSuperclass());
        // Add interceptor core methods
        Callback callbacks = getCallBack(advised);
        enhancer.setCallback(callbacks);
        enhancer.setClassLoader(classLoader);
        if(constructorArgs ! =null && constructorArgs.length > 0) {return enhancer.create(constructorArgTypes,constructorArgs);
        }
        return enhancer.create();
    }
    private Callback getCallBack(AdvisedSupport advised) {
        return newDynamicAdvisedIcnterceptor(advised.getList(),advised.getTargetSource()); }}Copy the code

CglibAopProxy is our core method for proxy object generation. Use Cglib to generate proxy classes. We can work with the previous IOC framework code. Comparison found that the difference lies in:

    Callback callbacks = getCallBack(advised);
    enhancer.setCallback(callbacks);
Copy the code

The callback is different from before, but wrote a getCallback () method, we take a look at what did getCallback DynamicAdvisedIcnterceptor inside.

The use of Cglib will not be introduced here. If you do not understand the function of callback, you need to learn by yourself.

public class DynamicAdvisedInterceptor implements MethodInterceptor{

    protected final List<AopMethodInterceptor> interceptorList;
    protected final TargetSource targetSource;

    public DynamicAdvisedInterceptor(List<AopMethodInterceptor> interceptorList, TargetSource targetSource) {
        this.interceptorList = interceptorList;
        this.targetSource = targetSource;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);
        returninvocation.proceed(); }}Copy the code

To note here, DynamicAdvisedInterceptor this class gclib is the MethodInterceptor interface, not our AopMethodInterceptor before.

We looked closely at the Intercept method and saw:

MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);
Copy the code

With this line of code, our entire logic is finally connected. It is this dynamic interceptor that delegates our enhanced method Invocation to Cglib to generate the proxy object.

At this point, the core functionality of our Aop is implemented.

AopBeanFactoryImpl

public class AopBeanFactoryImpl extends BeanFactoryImpl{

    private static final ConcurrentHashMap<String,AopBeanDefinition> aopBeanDefinitionMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,Object> aopBeanMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String name) throws Exception {
        Object aopBean = aopBeanMap.get(name);

        if(aopBean ! =null) {return aopBean;
        }
        if(aopBeanDefinitionMap.containsKey(name)){
            AopBeanDefinition aopBeanDefinition = aopBeanDefinitionMap.get(name);
            AdvisedSupport advisedSupport = getAdvisedSupport(aopBeanDefinition);
            aopBean = new CglibAopProxy(advisedSupport).getProxy();
            aopBeanMap.put(name,aopBean);
            return aopBean;
        }

        return super.getBean(name);
    }
    protected void registerBean(String name, AopBeanDefinition aopBeanDefinition){
        aopBeanDefinitionMap.put(name,aopBeanDefinition);
    }

    private AdvisedSupport getAdvisedSupport(AopBeanDefinition aopBeanDefinition) throws Exception {

        AdvisedSupport advisedSupport = new AdvisedSupport();
        List<String> interceptorNames = aopBeanDefinition.getInterceptorNames();
        if(interceptorNames ! =null && !interceptorNames.isEmpty()){
            for (String interceptorName : interceptorNames) {

                Advice advice = (Advice) getBean(interceptorName);

                Advisor advisor = new Advisor();
                advisor.setAdvice(advice);

                if(advice instanceofBeforeMethodAdvice){ AopMethodInterceptor interceptor = BeforeMethodAdviceAdapter.getInstants().getInterceptor(advisor);  advisedSupport.addAopMethodInterceptor(interceptor); }if(advice instanceofAfterRunningAdvice){ AopMethodInterceptor interceptor = AfterRunningAdviceAdapter.getInstants().getInterceptor(advisor);  advisedSupport.addAopMethodInterceptor(interceptor); } } } TargetSource targetSource =new TargetSource();
        Object object = getBean(aopBeanDefinition.getTarget());
        targetSource.setTagetClass(object.getClass());
        targetSource.setTagetObject(object);
        advisedSupport.setTargetSource(targetSource);
        returnadvisedSupport; }}Copy the code

AopBeanFactoryImpl is our factory class that generates proxy objects, inheriting the BeanFactoryImpl of the IoC container we implemented last time. The getBean method is overridden. If it is a faceted proxy class, we use the Aop framework to generate the proxy class. If it is a generic object, we use the original IoC container for dependency injection. GetAdvisedSupport is the data structure that captures the knowledge of the Aop framework.

The rest of the classes are relatively simple, we look at the source code. Has nothing to do with core logic.

Write a method to test it

We need to count the execution time of a method. What can we do about this need?

public class StartTimeBeforeMethod implements BeforeMethodAdvice{
    @Override
    public void before(Method method, Object[] args, Object target) {
        long startTime = System.currentTimeMillis();
        System.out.println("Start the clock."); ThreadLocalUtils.set(startTime); }}Copy the code
public class EndTimeAfterMethod implements AfterRunningAdvice {
    @Override
    public Object after(Object returnVal, Method method, Object[] args, Object target) {
        long endTime = System.currentTimeMillis();
        long startTime = ThreadLocalUtils.get();
        ThreadLocalUtils.remove();
        System.out.println("Method time:" + (endTime - startTime) + "ms");
        returnreturnVal; }}Copy the code

Before the method starts, record the time and save it in ThredLocal. At the end of the method, record the time and print the time difference. Complete the statistics.

The target class:

public class TestService {
    public void testMethod(a) throws InterruptedException {
        System.out.println("this is a test method");
        Thread.sleep(1000); }}Copy the code

Configuration file:

[{"name":"beforeMethod"."className":"com.xilidou.framework.aop.test.StartTimeBeforeMethod"
  },
  {
    "name":"afterMethod"."className":"com.xilidou.framework.aop.test.EndTimeAfterMethod"
  },
  {
    "name":"testService"."className":"com.xilidou.framework.aop.test.TestService"
  },
  {
    "name":"testServiceProxy"."className":"com.xilidou.framework.aop.core.ProxyFactoryBean"."target":"testService"."interceptorNames": ["beforeMethod"."afterMethod"]}]Copy the code

The test class:

public class MainTest {
    public static void main(String[] args) throws Exception {
        AopApplictionContext aopApplictionContext = new AopApplictionContext("application.json");
        aopApplictionContext.init();
        TestService testService = (TestService) aopApplictionContext.getBean("testServiceProxy"); testService.testMethod(); }}Copy the code

Our final execution result:

This is a test method 1015ms Process finished with exit code 0Copy the code

The Aop framework is now complete.

Afterword.

IoC and Aop are the two core features of Spring. I hope you can understand these two features in depth through my two articles.

Spring’s source code is complex and often frustrating to read, but as long as you stick with it and follow some proven methods. Still able to understand Spring code. And learn from it.

In the next article, I will tell you some ways to read open source code and my own experience, stay tuned.

The last

Github:github.com/diaozxin007…