AOP code weaving of 30 classes handwritten Spring core principles

Posted by bluntster on Tue, 14 Dec 2021 09:37:19 +0100

This article is excerpted from Spring 5 core principles

Previously, we have completed the functions of the three core modules of Spring IoC, DI and MVC, and ensured that the functions are available. The next step is to complete another core module of Spring - AOP, which is also the most difficult part.

1 basic configuration

First, in application Add the following custom configuration in properties as the basic configuration of Spring AOP:

#For multi aspect configuration, you can prefix the key
#For example, aspect logAspect.

#Slice expression#
pointCut=public .* com.tom.spring.demo.service..*Service..*(.*)
#Section class#
aspectClass=com.tom.spring.demo.aspect.LogAspect
#Slice pre notification#
aspectBefore=before
#Slice post notification#
aspectAfter=after
#Section exception notification#
aspectAfterThrow=afterThrowing
#Section exception type#
aspectAfterThrowingName=java.lang.Exception

To enhance our understanding, let's compare the native configuration of Spring AOP:

<bean id="xmlAspect" class="com.gupaoedu.aop.aspect.XmlAspect"></bean>

<!-- AOP to configure -->
<aop:config>

   <!-- Declare a slice and inject the slice Bean,amount to@Aspect -->
   <aop:aspect ref="xmlAspect">
      <!-- Configuring a pointcut is equivalent to@Pointcut -->
      <aop:pointcut expression="execution(* com.gupaoedu.aop.service..*(..))" id="simplePointcut"/>
      <!-- Configure notifications, equivalent to@Before,@After,@AfterReturn,@Around,@AfterThrowing -->
      <aop:before pointcut-ref="simplePointcut" method="before"/>
      <aop:after pointcut-ref="simplePointcut" method="after"/>
      <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
      <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
   </aop:aspect>

</aop:config>

For convenience, we use the properties file instead of XML to simplify the operation.

2 AOP core principle v1 0 version

The basic implementation principle of AOP is to use the dynamic agent mechanism to create a new agent class to complete code weaving, so as to achieve the purpose of code function enhancement. If you don't know much about the principle of dynamic agent, you can go back to the special article on dynamic agent mode in the "design patterns should be learned this way" series I updated some time ago. So how does Spring AOP work with dynamic proxies? In fact, the main function of Spring is to complete decoupling, separate the code logic we need to enhance into special classes, then associate these separated logic through the declaration configuration file, and finally merge them to run together. In order to save this relationship in the Spring container, we can simply understand that Spring uses a Map to save this relationship. The key of Map is the target method we want to call, and the value of Map is the method we want to weave in. However, the methods to be woven are in sequence, so we need to mark the position of the weaving methods. The logic woven in front of the target method is called pre notification, the logic woven behind the target method is called post notification, and the logic that needs to be woven in case of exception in the target method is called exception notification. The specific design of Map is as follows:

private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>();

I'll write a simple ApplicationContex in full below. You can refer to it:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;



public class GPApplicationContext {
    private Properties contextConfig = new Properties();
    private Map<String,Object> ioc = new HashMap<String,Object>();
    //It is used to save the correspondence between the corresponding Method and Advice in the configuration file
    private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>();


    public GPApplicationContext(){
		
		   //For demonstration purposes, manually initialize a Bean
			 
        ioc.put("memberService", new MemberService());

        doLoadConfig("application.properties");

        doInitAopConfig();

    }

    public Object getBean(String name){
        return createProxy(ioc.get(name));
    }


    private Object createProxy(Object instance){
        return new GPJdkDynamicAopProxy(instance).getProxy();
    }

    //Load profile
    private void doLoadConfig(String contextConfigLocation) {
        //Directly find the path where the Spring main configuration file is located from the classpath
        //And read it out and put it in the Properties object
        //Relative to scanpackage = com gupaoedu. The demo is saved from the file to memory
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInitAopConfig() {

        try {
            Class apectClass = Class.forName(contextConfig.getProperty("aspectClass"));
            Map<String,Method> aspectMethods = new HashMap<String,Method>();
            for (Method method : apectClass.getMethods()) {
                aspectMethods.put(method.getName(),method);
            }

            //The PonintCut expression resolves to a regular expression
            String pointCut = contextConfig.getProperty("pointCut")
                    .replaceAll("\\.","\\\\.")
                    .replaceAll("\\\\.\\*",".*")
                    .replaceAll("\\(","\\\\(")
                    .replaceAll("\\)","\\\\)");
            Pattern pointCutPattern = Pattern.compile(pointCut);

            for (Map.Entry<String,Object> entry : ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                //Loop to find all methods
                for (Method method : clazz.getMethods()) {
                    //Save method name
                    String methodString = method.toString();
                    if(methodString.contains("throws")){
                        methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                    }
                    Matcher matcher = pointCutPattern.matcher(methodString);
                    if(matcher.matches()){
                        Map<String,Method> advices = new HashMap<String,Method>();
                        if(!(null == contextConfig.getProperty("aspectBefore") || "".equals( contextConfig.getProperty("aspectBefore")))){
                            advices.put("before",aspectMethods.get(contextConfig.getProperty("aspectBefore")));
                        }
                        if(!(null ==  contextConfig.getProperty("aspectAfter") || "".equals( contextConfig.getProperty("aspectAfter")))){
                            advices.put("after",aspectMethods.get(contextConfig.getProperty("aspectAfter")));
                        }
                        if(!(null == contextConfig.getProperty("aspectAfterThrow") || "".equals( contextConfig.getProperty("aspectAfterThrow")))){
                            advices.put("afterThrow",aspectMethods.get(contextConfig.getProperty("aspectAfterThrow")));
                        }
                        methodAdvices.put(method,advices);
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    class GPJdkDynamicAopProxy implements GPInvocationHandler {
        private Object instance;
        public GPJdkDynamicAopProxy(Object instance) {
            this.instance = instance;
        }

        public Object getProxy() {
            return Proxy.newProxyInstance(instance.getClass().getClassLoader(),instance.getClass().getInterfaces(),this);
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object aspectObject = Class.forName(contextConfig.getProperty("aspectClass")).newInstance();
            Map<String,Method> advices = methodAdvices.get(instance.getClass().getMethod(method.getName(),method.getParameterTypes()));
            Object returnValue = null;
            advices.get("before").invoke(aspectObject);
            try {
                returnValue = method.invoke(instance, args);
            }catch (Exception e){
                advices.get("afterThrow").invoke(aspectObject);
                e.printStackTrace();
                throw e;
            }
            advices.get("after").invoke(aspectObject);
            return returnValue;
        }
    }

}

Test code:

public class MemberServiceTest {


    public static void main(String[] args) {
        GPApplicationContext applicationContext = new GPApplicationContext();
        IMemberService memberService = (IMemberService)applicationContext.getBean("memberService");

        try {

            memberService.get("1");
            memberService.save(new Member());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

We can fully demonstrate the core principles of Spring AOP through a few hundred lines of code. Is it very simple? Of course, the little friends still have to do it by themselves, ha, experience it personally, so as to be impressed. Next, let's continue to improve and upgrade Spring AOP 1.0 to 2.0. I wrote version 2.0 by completely simulating the original design of Spring. I hope it can bring you a different handwriting experience and a deeper understanding of the principle of Spring AOP.

3. Complete AOP top-level design

3.1 GPJoinPoint

Define a pointcut abstraction, which is the basic unit of AOP. We can understand that this is additional information of a business method. It is conceivable that the pointcut should include the business method itself, the argument list and the instance object to which the method belongs. You can also add custom attributes in GPJoinPoint. See the following code:

package com.tom.spring.formework.aop.aspect;

import java.lang.reflect.Method;

/**
 * Callback the connection point, through which you can get all the information of the proxy business method
 */
public interface GPJoinPoint {

    Method getMethod(); //Business method itself

    Object[] getArguments();  //List of arguments for the method

    Object getThis(); //The instance object to which the method belongs

    //Add custom properties in JoinPoint
    void setUserAttribute(String key, Object value);
    //Get a property value from the added custom property
    Object getUserAttribute(String key);

}

3.2 GPMethodInterceptor

Method interceptor is the basic unit of AOP code enhancement. Its subclasses mainly include GPMethodBeforeAdvice, GPAfterReturningAdvice and GPAfterThrowingAdvice.

package com.tom.spring.formework.aop.intercept;

/**
 * Method interceptor top-level interface
 */ 
public interface GPMethodInterceptor{
    Object invoke(GPMethodInvocation mi) throws Throwable;
}

3.3 GPAopConfig

Define the encapsulation object of AOP configuration information to facilitate mutual transmission in subsequent code.

package com.tom.spring.formework.aop;

import lombok.Data;

/**
 * AOP Configuration encapsulation
 */
@Data
public class GPAopConfig {
//The following configuration corresponds to the properties in the properties file one by one
    private String pointCut;  //Slice expression
    private String aspectBefore;  //Pre notification method name
    private String aspectAfter;  //Post notification method name
    private String aspectClass;  //Facet class to weave in
    private String aspectAfterThrow;  //Exception notification method name
    private String aspectAfterThrowingName;  //Exception type to be notified
}

3.4 GPAdvisedSupport

Gpadvised support mainly completes the analysis of AOP configuration. The pointCutMatch() method is used to judge whether the target class conforms to the facet rules, so as to determine whether the proxy class needs to be generated and enhance the target method. The getInterceptorsAndDynamic- InterceptionAdvice() method mainly encapsulates the method to be recalled into an interceptor chain according to the AOP configuration, and returns it for external acquisition.

package com.tom.spring.formework.aop.support;

import com.tom.spring.formework.aop.GPAopConfig;
import com.tom.spring.formework.aop.aspect.GPAfterReturningAdvice;
import com.tom.spring.formework.aop.aspect.GPAfterThrowingAdvice;
import com.tom.spring.formework.aop.aspect.GPMethodBeforeAdvice;

import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * It is mainly used to parse and encapsulate AOP configuration
 */
public class GPAdvisedSupport {
    private Class targetClass;
    private Object target;
    private Pattern pointCutClassPattern;

    private transient Map<Method, List<Object>> methodCache;

    private GPAopConfig config;

    public GPAdvisedSupport(GPAopConfig config){
        this.config = config;
    }

    public Class getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception {
        List<Object> cached = methodCache.get(method);

        //If the cache misses, proceed to the next step
        if (cached == null) {
           Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
           cached = methodCache.get(m);
            //Store in cache
            this.methodCache.put(m, cached);
        }
        return cached;
    }

    public boolean pointCutMatch(){
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }

    private void parse(){
        //pointCut expression
        String pointCut = config.getPointCut()
                .replaceAll("\\.","\\\\.")
                .replaceAll("\\\\.\\*",".*")
                .replaceAll("\\(","\\\\(")
                .replaceAll("\\)","\\\\)");

        String pointCutForClass = pointCut.substring(0,pointCut.lastIndexOf("\\(") - 4);
        pointCutClassPattern = Pattern.compile("class " + pointCutForClass.substring (pointCutForClass.lastIndexOf(" ")+1));

        methodCache = new HashMap<Method, List<Object>>();
        Pattern pattern = Pattern.compile(pointCut);

        try {
            Class aspectClass = Class.forName(config.getAspectClass());
            Map<String,Method> aspectMethods = new HashMap<String,Method>();
            for (Method m : aspectClass.getMethods()){
                aspectMethods.put(m.getName(),m);
            }

            //The methods obtained here are native methods
            for (Method m : targetClass.getMethods()){

                String methodString = m.toString();
                if(methodString.contains("throws")){
                    methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                }
                Matcher matcher = pattern.matcher(methodString);
                if(matcher.matches()){
                    //Classes that meet the section rules are added to the AOP configuration
                    List<Object> advices = new LinkedList<Object>();
                    //Before advice 
                    if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore().trim()))) {
                        advices.add(new GPMethodBeforeAdvice(aspectMethods.get (config.getAspectBefore()), aspectClass.newInstance()));
                    }
                    //Post notification
                    if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter(). trim()))) {
                        advices.add(new GPAfterReturningAdvice(aspectMethods.get (config.getAspectAfter()), aspectClass.newInstance()));
                    }
                    //Exception notification
                    if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow().trim()))) {
                        GPAfterThrowingAdvice afterThrowingAdvice = new GPAfterThrowingAdvice (aspectMethods.get(config.getAspectAfterThrow()), aspectClass.newInstance());
                        afterThrowingAdvice.setThrowingName(config.getAspectAfterThrowingName());
                        advices.add(afterThrowingAdvice);
                    }
                    methodCache.put(m,advices);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.5 GPAopProxy

GPAopProxy is the top-level interface of the proxy factory. There are two main subclasses: GPCglibAopProxy and GPJdkDynamicAopProxy, which implement CGlib proxy and JDK Proxy respectively.

package com.tom.spring.formework.aop;
/**
 * The top-level interface of the agent factory provides the top-level entry to obtain the agent object 
 */
//JDK dynamic proxy is used by default
public interface GPAopProxy {
//Get a proxy object
    Object getProxy();
//Get a proxy object through a custom class loader
    Object getProxy(ClassLoader classLoader);
}

3.6 GPCglibAopProxy

CglibAopProxy is not implemented in this article. Interested "small partners" can try it by themselves.

package com.tom.spring.formework.aop;

import com.tom.spring.formework.aop.support.GPAdvisedSupport;

/**
 * Use CGlib API to generate proxy classes. No examples are given here
 * Interested "small partners" can be realized by themselves
 */
public class GPCglibAopProxy implements GPAopProxy {
    private GPAdvisedSupport config;

    public GPCglibAopProxy(GPAdvisedSupport config){
        this.config = config;
    }

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

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

3.7 GPJdkDynamicAopProxy

Let's look at the implementation of GPJdkDynamicAopProxy. The main function is in the invoke() method. In terms of the amount of code, it's not much. It's mainly by calling the getinterceptorsanddynamicinception advice() method of GPAdvisedSupport to obtain the interceptor chain. In the target class, each enhanced target method corresponds to an interceptor chain.

package com.tom.spring.formework.aop;

import com.tom.spring.formework.aop.intercept.GPMethodInvocation;
import com.tom.spring.formework.aop.support.GPAdvisedSupport;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * Generate proxy classes using JDK Proxy API
 */
public class GPJdkDynamicAopProxy implements GPAopProxy,InvocationHandler {
    private GPAdvisedSupport config;

    public GPJdkDynamicAopProxy(GPAdvisedSupport config){
        this.config = config;
    }

    //Pass in native objects
    public Object getProxy(){
        return getProxy(this.config.getTargetClass().getClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(classLoader,this.config.getTargetClass().getInterfaces(),this);
    }

    //The invoke() method is the key entry point for executing the proxy
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//Encapsulate each JoinPoint, that is, the proxy's business Method, into an interceptor and combine it into an interceptor chain
        List<Object> interceptorsAndDynamicMethodMatchers = config.getInterceptorsAndDynamicInterceptionAdvice(method,this.config.getTargetClass());
//The interceptor chain is executed by the processed() method of MethodInvocation
        GPMethodInvocation invocation = new GPMethodInvocation(proxy,this.config.getTarget(), method,args,this.config.getTargetClass(),interceptorsAndDynamicMethodMatchers);
        return invocation.proceed();
    }
}

As can be seen from the code, the interceptor chain obtained from gpadvised support is passed into the construction method of GPMethodInvocation as a parameter. So what is done to the method chain in GPMethodInvocation?

3.8 GPMethodInvocation

The code of GPMethodInvocation is as follows:

package com.tom.spring.formework.aop.intercept;

import com.tom.spring.formework.aop.aspect.GPJoinPoint;

import java.lang.reflect.Method;
import java.util.List;

/**
 * Execute interceptor chain, which is equivalent to the function of reflective method invocation in Spring
 */
public class GPMethodInvocation implements GPJoinPoint {

    private Object proxy; //Proxy object
    private Method method; //Target method of agent
    private Object target; //Target object of proxy
    private Class<?> targetClass; //Target class of agent
    private Object[] arguments; //List of arguments for the proxy's method
    private List<Object> interceptorsAndDynamicMethodMatchers; //Callback method chain

//Save custom attributes
private Map<String, Object> userAttributes;


    private int currentInterceptorIndex = -1;

    public GPMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
                              Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = method;
        this.arguments = arguments;
        this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
    }

    public Object proceed() throws Throwable {
//If the Interceptor has finished executing, execute joinPoint
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.method.invoke(this.target,this.arguments);
        }
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
//If you want to dynamically match joinpoints
if (interceptorOrInterceptionAdvice instanceof GPMethodInterceptor) {
            GPMethodInterceptor mi = (GPMethodInterceptor) interceptorOrInterceptionAdvice;
            return mi.invoke(this);
        } else {
//Execute the current intercetor

            return proceed();
        }

    }

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

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

    @Override
    public Object getThis() {
        return this.target;
    }

public void setUserAttribute(String key, Object value) {
      if (value != null) {
          if (this.userAttributes == null) {
              this.userAttributes = new HashMap<String,Object>();
          }
          this.userAttributes.put(key, value);
      }
      else {
          if (this.userAttributes != null) {
              this.userAttributes.remove(key);
          }
      }
  }


  public Object getUserAttribute(String key) {
      return (this.userAttributes != null ? this.userAttributes.get(key) : null);
  }

}

As can be seen from the code, the processed () method is the key to MethodInvocation. In process (), judge first. If the interceptor chain is empty, it means that the target method does not need to be enhanced. Call the target method directly and return. If the interceptor chain is not empty, the methods in the interceptor chain will be executed in sequence until all the methods in the interceptor chain are executed.

4 design AOP basic implementation

4.1 GPAdvice

As the top-level interface design of all callback notifications, GPAdvice is designed as a specification and does not implement any functions in the Mini version in order to be consistent with the native Spring as much as possible.

/**
 * Callback notification top-level interface
 */
public interface GPAdvice {

}

4.2 GPAbstractAspectJAdvice

The GPAbstractAspectJAdvice class is designed using template pattern to encapsulate the general logic of interceptor callback. It mainly encapsulates reflection dynamic call methods. Its subclasses only need to control the call order.

package com.tom.spring.formework.aop.aspect;

import java.lang.reflect.Method;

/**
 * Encapsulates the general logic of interceptor callback. In the Mini version, it mainly encapsulates the reflection dynamic call method
 */
public abstract class GPAbstractAspectJAdvice implements GPAdvice {

    private Method aspectMethod;
    private Object aspectTarget;

    public GPAbstractAspectJAdvice(
            Method aspectMethod, Object aspectTarget) {
            this.aspectMethod = aspectMethod;
            this.aspectTarget = aspectTarget;
    }

    //Reflection dynamic call method
    protected Object invokeAdviceMethod(GPJoinPoint joinPoint,Object returnValue,Throwable ex)
            throws Throwable {
        Class<?> [] paramsTypes = this.aspectMethod.getParameterTypes();
        if(null == paramsTypes || paramsTypes.length == 0) {
            return this.aspectMethod.invoke(aspectTarget);
        }else {
            Object[] args = new Object[paramsTypes.length];
            for (int i = 0; i < paramsTypes.length; i++) {
                if(paramsTypes[i] == GPJoinPoint.class){
                    args[i] = joinPoint;
                }else if(paramsTypes[i] == Throwable.class){
                    args[i] = ex;
                }else if(paramsTypes[i] == Object.class){
                    args[i] = returnValue;
                }
            }
            return this.aspectMethod.invoke(aspectTarget,args);
        }
    }
}

4.3 GPMethodBeforeAdvice

GPMethodBeforeAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the calling order of pre notification in invoke().

package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * Implementation of pre notification
 */
public class GPMethodBeforeAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private GPJoinPoint joinPoint;

    public GPMethodBeforeAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    public void before(Method method, Object[] args, Object target) throws Throwable {
        invokeAdviceMethod(this.joinPoint,null,null);
    }

    public Object invoke(GPMethodInvocation mi) throws Throwable {
        this.joinPoint = mi;
        this.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

4.4 GPAfterReturningAdvice

GPAfterReturningAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the calling order of post notification in invoke().

package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * Implementation of post notification
 */
public class GPAfterReturningAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private GPJoinPoint joinPoint;
    public GPAfterReturningAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    @Override
    public Object invoke(GPMethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.joinPoint = mi;
        this.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }

    public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable{
        invokeAdviceMethod(joinPoint,returnValue,null);
    }

}

4.5 GPAfterThrowingAdvice

GPAfterThrowingAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the call order of exception notification in invoke().

package com.tom.spring.formework.aop.aspect;

import com.tom.spring.formework.aop.intercept.GPMethodInterceptor;
import com.tom.spring.formework.aop.intercept.GPMethodInvocation;

import java.lang.reflect.Method;

/**
 * Implementation of exception notification
 */
public class GPAfterThrowingAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor {

    private String throwingName;
    private GPMethodInvocation mi;

    public GPAfterThrowingAdvice(Method aspectMethod, Object target) {
        super(aspectMethod, target);
    }

    public void setThrowingName(String name) {
        this.throwingName = name;
    }

    @Override
    public Object invoke(GPMethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        }catch (Throwable ex) {
            invokeAdviceMethod(mi,null,ex.getCause());
            throw ex;
        }
    }
}

Interested "partners" can refer to the Spring source code and implement the call logic around the notification by themselves.

4.6 accessing getBean() method

In the above code, we have completed the core functions of the Spring AOP module, so how to integrate it into the IoC container next? Find the getBean() method of GPApplicationContext. We know that the method responsible for Bean initialization in getBean() is actually instantiateBean(). During initialization, we can determine whether to return the native Bean or Proxy Bean. The code implementation is as follows:

//Pass a BeanDefinition and return an instance Bean
private Object instantiateBean(GPBeanDefinition beanDefinition){
    Object instance = null;
    String className = beanDefinition.getBeanClassName();
    try{

        //Because you can only determine whether a Class has an instance according to Class
        if(this.singletonBeanCacheMap.containsKey(className)){
            instance = this.singletonBeanCacheMap.get(className);
        }else{
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            GPAdvisedSupport config = instantionAopConfig(beanDefinition);
            config.setTargetClass(clazz);
            config.setTarget(instance);

            if(config.pointCutMatch()) {
                instance = createProxy(config).getProxy();
            }
		  this.factoryBeanObjectCache.put(className,instance);
            this.singletonBeanCacheMap.put(beanDefinition.getFactoryBeanName(),instance);
        }

        return instance;
    }catch (Exception e){
        e.printStackTrace();
    }

    return null;
}

private GPAdvisedSupport instantionAopConfig(GPBeanDefinition beanDefinition) throws  Exception{

    GPAopConfig config = new GPAopConfig();
    config.setPointCut(reader.getConfig().getProperty("pointCut"));
    config.setAspectClass(reader.getConfig().getProperty("aspectClass"));
    config.setAspectBefore(reader.getConfig().getProperty("aspectBefore"));
    config.setAspectAfter(reader.getConfig().getProperty("aspectAfter"));
    config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow"));
    config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName"));

    return new GPAdvisedSupport(config);
}

private GPAopProxy createProxy(GPAdvisedSupport config) {
    Class targetClass = config.getTargetClass();
    if (targetClass.getInterfaces().length > 0) {
        return new GPJdkDynamicAopProxy(config);
    }
    return new GPCglibAopProxy(config);
}

As you can see from the above code, calling createProxy() in the instantiateBean() method determines the invocation strategy of the proxy factory, and then invokes the proxy() method of the agent factory to create the proxy object. The final proxy object will be encapsulated in BeanWrapper and saved to IoC container.

5. Weaving business code

Through the previous code writing, all core modules and underlying logic have been realized, "everything is ready, only the east wind." Next, it's "the time to witness miracles". Let's weave business code and do a test. Create a LogAspect class to monitor business methods. It mainly records the call log of the target method, and obtains the target method name, argument list and the time consumed by each call.

5.1 LogAspect

The code of LogAspect is as follows:

package com.tom.spring.demo.aspect;

import com.tom.spring.formework.aop.aspect.GPJoinPoint;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * Define an embedded slice logic, that is, the logic to be enhanced for the target proxy object
 * This class is mainly used to monitor method calls and monitor the time consumed by each execution of the target method
 */
@Slf4j
public class LogAspect {

    //Before calling a method, execute the before() method
    public void before(GPJoinPoint joinPoint){
        joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis());
        //The logic in this method is written by ourselves
        log.info("Invoker Before Method!!!" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()));
    }

    //After a method is called, the after() method is executed
    public void after(GPJoinPoint joinPoint){
        log.info("Invoker After Method!!!" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()));
        long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName());
        long endTime = System.currentTimeMillis();
        System.out.println("use time :" + (endTime - startTime));
    }

    public void afterThrowing(GPJoinPoint joinPoint, Throwable ex){
        log.info("An exception occurred" +
                "\nTargetObject:" +  joinPoint.getThis() +
                "\nArgs:" + Arrays.toString(joinPoint.getArguments()) +
                "\nThrows:" + ex.getMessage());
    }
}

Through the above code, we can find that each callback method adds a parameter GPJoinPoint. Remember what GPJoinPoint is? In fact, GPMethodInvocation is the implementation class of GPJoinPoint. GPMethodInvocation is instantiated in the invoke() method of GPJdkDynamicAopPorxy, that is, the business method of each proxy object will correspond to a GPMethodInvocation instance. That is, the lifecycle of MethodInvocation corresponds to the lifecycle of the business method in the proxy object. As we have seen earlier, calling the setUserAttribute() method of GPJoinPoint can customize the attribute in GPJoinPoint, and calling the getUserAttribute() method can obtain the value of the custom attribute. In the before() method of LogAspect, set startTime in GPJoinPoint and assign it as system time, that is, record the context from the start time of method call to MethodInvocation. Obtain startTime in the after() method of LogAspect, and save the system time obtained again to endTime. In the AOP interceptor chain callback, the before() method must be called before the after() method, so the system time acquired by the two time will form a time difference, which is the time consumed by the execution of the business method. Through this time difference, we can judge the performance consumption of business methods in unit time. Is it cleverly designed? In fact, almost all system monitoring frameworks on the market are based on such an idea, which can be highly decoupled and reduce code intrusion.

5.2 IModifyService

In order to demonstrate the exception callback notification, we added the function of throwing an exception to the add() method of the previously defined IModifyService interface. See the following code implementation:

package com.tom.spring.demo.service;

/**
 * Add, delete and modify business
  */
public interface IModifyService {

   /**
    * increase
    */
   String add(String name, String addr) throws Exception;
   
   /**
    * modify
    */
   String edit(Integer id, String name);
   
   /**
    * delete
    */
   String remove(Integer id);
   
}

5.3 ModifyService

The code of ModifyService is as follows:

package com.tom.spring.demo.service.impl;

import com.tom.spring.demo.service.IModifyService;
import com.tom.spring.formework.annotation.GPService;

/**
 * Add, delete and modify business
 */
@GPService
public class ModifyService implements IModifyService {

   /**
    * increase
    */
   public String add(String name,String addr) throws Exception {
      throw new Exception("Deliberately throw an exception to test whether the aspect notification is effective");
//    return "modifyService add,name=" + name + ",addr=" + addr;
   }

   /**
    * modify
    */
   public String edit(Integer id,String name) {
      return "modifyService edit,id=" + id + ",name=" + name;
   }

   /**
    * delete
    */
   public String remove(Integer id) {
      return "modifyService id=" + id;
   }
}

6. Operation effect demonstration

Enter in the browser http://localhost/web/add.json?name=Tom&addr=HunanChangsha , you can visually and clearly see the exception information thrown by the Service layer, as shown in the following figure.

The console output is shown in the figure below.

Through the console output, we can see that the exception notification successfully captured the exception information, triggered GPMethodBeforeAdvice and GPAfterThrowingAdvice, but did not trigger GPAfterReturningAdvice, which is in line with our expectations. Now do another test and enter http://localhost/web/query.json?name=Tom The results are shown in the figure below:

The console output is shown in the figure below:

Through the console output, we can see that the pre notification and post notification are captured respectively, and the relevant information is printed, which is in line with our expectations.

So far, the AOP module has been completed. Is there a small sense of achievement and eager to try? In the implementation of the whole Mini version, some details are not considered too much. More importantly, we hope to provide "friends" with an idea of learning the source code. Handwritten source code is not to build wheels repeatedly, nor to install "tall". In fact, it is just a learning method we recommend to you. Pay attention to WeChat official account Tom bomb structure and reply to "Spring" to get the complete source code.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!

It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!

Topics: Java