Simple implementation of SpringMvc handwriting - AOP aspect programming

Posted by Unforgiven on Wed, 15 Dec 2021 21:10:31 +0100

For those who have read the previous article, please move on to the previous article.
This article is suitable for people who have used AOP aspect programming.

What is Aop

AOP (Aspect Oriented Programming) is short for Aspect Oriented Programming.

AOP is implemented by precompiling and runtime dynamic agent. The programming idea of dynamically weaving the code into the specified method and location of the class at runtime without modifying the source code is aspect oriented programming.

Role of AOP:

Do something that many classes are doing and do repeatedly to reduce duplicate code
Logging, performance statistics, transaction processing, exception handling

Notification of AOP scenarios:

  • Pre notification, method execution before calling, annotation @Before
  • Post notification, call after method execution, annotate @After
  • Around the notification, before and after the execution of the method, annotate @Around
  • Return notification, method returns, call @AfterReturning
  • Exception notification, method execution exception call, annotation @ AfterThrowing

After knowing what this is, let's implement AOP in the code in the previous section

AOP section handwritten simple version

1. Preparation in advance

  • Structural changes, newly added aspect class LogAspect, aop call chain TAdvice, aop aspect information TAopConfig, proxy encapsulation class tjdkddynamicaaopproxy, model is not woven temporarily, and is only used for entity classes
  • application. The properties configuration is added. Here, the configuration is written directly, eliminating the need to parse the annotation to get the corresponding content
#AOP tangent point
pointCut=public .* com.xxx.demo.service.impl..*(.*)
#Classes executed by AOP
aspectClass=com.xxx.demo.aspect.LogAspect
#AOP pre method
aspectBefore=before
#AOP post method
aspectAfter=after
#AOP exception method
aspectAfterThrowing=afterThrowing
  • New aspect class LogAspect
public class LogAspect {
    //Method before executing the before method
    public void before(JoinPoint point) {
        System.out.println("Call before method:"+point.toString());
    }
    //Method is followed by the after method
    public void after(JoinPoint point) {
        System.out.println("Call after method:"+point.toString());
    }
    public void afterThrowing(JoinPoint point) {
        System.out.println("An unexpected call occurred:"+point.toString());
    }
}
  • The aop directory stores the original class information
/**
 * AOP Faceted call chain encapsulation
 */
@Data
public class TAdvice {
    private Object aspectClass;
    private Method aspectMethod;
    private String throwName;

    public TAdvice(Object aspectClass, Method aspectMethod) {
        this.aspectClass = aspectClass;
        this.aspectMethod = aspectMethod;
    }
}
  • Facet request information JoinPoint, the source code is improved
@Data
public class JoinPoint {
    Object target;
    Method method;
    Object[] args;
    Object result;
    String throwName;
    public Object proceed() throws Exception {
        return method.invoke(target,this);
    }
}

2.TestApplicationContext context content modification

  • Initialize doInstance() code and modify initAopConfig
 private void doInstance() {
     for (String className : this.classNames) {
         try {
             Class<?> clazz = Class.forName(className);

             //Only annotated classes are initialized
             if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
                 continue;
             }
             //If the AOP aspect conditions are met, the proxy object should be generated here, and the real call should have the invoke implementation
             Object instance = initAopConfig(clazz);

             //Save to IOC container beanname - > instance to generate bean name with lowercase initial
             String beanName = toFristLowerCase(clazz.getSimpleName());
             //If it is a service, check whether there is a user-defined name
             if(clazz.isAnnotationPresent(TService.class)){
                 TService service = clazz.getAnnotation(TService.class);
                 if(!"".equals(service.value())){
                     beanName = service.value();
                 }
             }

             //For example, the same beanName will not be considered for the time being, which mainly reflects the idea
             this.ioc.put(beanName,instance);

             //If the interface is implemented, the full path of the interface needs to be mapped to the service
             Class<?>[] interfaces = clazz.getInterfaces();
             for (Class<?> i : interfaces) {
                 this.ioc.put(i.getName(),instance);
             }

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
  • initAopConfig(clazz);AOP initialization
private Object initAopConfig(Class<?> clazz) throws Exception {
    Object instance = clazz.newInstance();

    //Get the pointcut regular matching full path class name of the facet. If the matching is successful, the proxy class is generated. The configuration can be written here
    String pointCut = this.contextConfig.getProperty("pointCut");
    //Replace the symbols in the string with recognizable regular symbols
	//public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
    String regxMethodStr = pointCut.replaceAll("\\.","\\\\.")
            .replaceAll("\\\\.\\*",".*")
            .replaceAll("\\(","\\\\(")
            .replaceAll("\\)","\\\\)");
    //The class name is class com xxx. demo. service. impl. Helloserviceimp matches the class name as follows
    //public .* com\.xxx\.demo\.service\.impl\..*
    String regxClassStr = regxMethodStr.substring(0,regxMethodStr.lastIndexOf("\\("));
    //class com\.xxx\.demo\.service\.impl\..*
    Pattern classPattern = Pattern.compile("class " + regxClassStr.substring(regxClassStr.lastIndexOf(" ")+1));
    //Regularity of classes
    if(classPattern.matcher(instance.getClass().toString()).matches()){
        //If the match is successful, the proxy object is created and the facet method is saved
        TAopConfig config = new TAopConfig();
        config.setPointCut(pointCut);
        config.setAspectClass(this.contextConfig.getProperty("aspectClass"));
        config.setAspectBefore(this.contextConfig.getProperty("aspectBefore"));
        config.setAspectAfter(this.contextConfig.getProperty("aspectAfter"));
        config.setAspectAfterThrowing(this.contextConfig.getProperty("aspectAfterThrowing"));
        //The regular public of the matching method* com\. xxx\. demo\. service\. impl\..*\ (.*\)
        Pattern methodPattern = Pattern.compile(regxMethodStr);
        config.setMethodPattern(methodPattern);
        config.setTarget(instance);
        //Parsing facet information when saving the original class
        config.setTargetClass(clazz);
        //Match build proxy object on
        instance = new TJDKDynamicAopProxy(config).getProxy();
    }
    return instance;
}

3. Save the original class and section information

@Data
public class TAopConfig {
    //Section information saving
    String pointCut;
    String aspectClass;
    String aspectBefore;
    String aspectAfter;
    String aspectAfterThrowing;

    Pattern methodPattern;

    //Original instance and clazz information
    Object target;
    Class<?> targetClass;

    Map<Method,Map<String,TAdvice>> adviceCacheMap = new HashMap<Method, Map<String, TAdvice>>();

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        //Make a map of the section information and call invoke
        parse();
    }

    //Make a map of the section information and call invoke
    /**
     * Initialize facet class
     * Get the corresponding method and store it in the map
     * adopt
     */
    private void parse() {
        try {
        	//Initialize facet class
            Class<?> clazz = Class.forName(this.aspectClass);
            Method[] methods = clazz.getMethods();
            Map<String,Method> aspectMap = new HashMap<String, Method>();
            for (Method method : methods) {
                aspectMap.put(method.getName(),method);
            }
            Map<String,TAdvice> adviceMap = new HashMap<String, TAdvice>();
            Object aspectInstance = clazz.newInstance();
            //Extract all methods that meet the pointcut class
            for (Method method : this.targetClass.getMethods()) {
                String methodName = method.toString();
                if(methodName.contains("throws")){
                    methodName = methodName.substring(0,methodName.lastIndexOf("throws")).trim();
                }
                //If the method satisfies the pointcut, the code is woven in
                //public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
                if(methodPattern.matcher(methodName).matches()){
                    if(null != this.aspectBefore){
                        adviceMap.put("before",new TAdvice(aspectInstance,aspectMap.get(this.aspectBefore)));
                    }

                    if(null != this.aspectAfter){
                        adviceMap.put("after",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfter)));
                    }

                    if(null != this.aspectAfterThrowing){
                        adviceMap.put("afterThrow",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfterThrowing)));
                    }
                    this.adviceCacheMap.put(method,adviceMap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //Method is actually a proxy object method. You need to go to the original targetClass to get the original method to find the mapping relationship in the Map
    public  Map<String, TAdvice> getAdviceMap(Method method) throws NoSuchMethodException {

            Map<String, TAdvice> adviceMap = adviceCacheMap.get(method);
            if(adviceMap == null){
                Method method1 = this.targetClass.getMethod(method.getName(), method.getParameterTypes());
                adviceMap = adviceCacheMap.get(method1);
                this.adviceCacheMap.put(method,adviceMap);
            }
            if(adviceMap == null){
                return new HashMap<String, TAdvice>();
            }
            return adviceMap;
    }
}

4. Proxy encapsulation class

public class TJDKDynamicAopProxy implements InvocationHandler {

    private TAopConfig config;

    public TJDKDynamicAopProxy(TAopConfig config) {
        this.config = config;
    }

    /**
     * The agent will come to this method
     * 1.Get the aspect AopConfig configuration, section information and original instance class
     * 2.Save the method - > List < taopconfig > configuration to multiple aspects of the map
     * 3.invoke in turn
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        Map<String, TAdvice> adviceMap = new HashMap<String, TAdvice>();

        Object result = null;
        JoinPoint joinPoint = new JoinPoint();
        joinPoint.setArgs(args);
        try {
        	//Get the section information map stored in aopConfig
            adviceMap = config.getAdviceMap(method);
            invokeAspect(adviceMap.get("before"), joinPoint);
			//Real method call
            result = method.invoke(config.getTarget(), args);
            joinPoint.setResult(result);
            invokeAspect(adviceMap.get("after"), joinPoint);
        } catch (Exception e) {
            joinPoint.setThrowName(e.getCause().getMessage());
            invokeAspect(adviceMap.get("afterThrow"), joinPoint);
            throw e;
        }
        return result;
    }

    private void invokeAspect(TAdvice advice, JoinPoint joinPoint) {
        try {
            if (advice == null) return;
            //Normally, it's joinpoint Invoke, which can be supplemented after simplifying the source code
            advice.getAspectMethod().invoke(advice.getAspectClass(), joinPoint);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * JDK Creating a proxy object through an interface is not considered temporarily if there is no interface
     *
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                this.config.getTargetClass().getInterfaces(), this);
    }
}

5. Run test

Or the previous test

public static void main(String[] args) {
    TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
    HelloAction action = (HelloAction) context.getBean(HelloAction.class);
    String hello = action.hello("Zhang San");
    System.out.println(hello);
}

//Printout
application is init
 Call before method:JoinPoint(target=null, method=null, args=[Zhang San], result=null, throwName=null)
Call after method:JoinPoint(target=null, method=null, args=[Zhang San], result=my name is Zhang San, throwName=null)
my name is Zhang San

summary

When instantiating an object, judge whether the class is full or not. If so, create a proxy class, and store the information of the original class in the corresponding proxy class, so as to obtain the information of the original class in invoke and call the real method

The above contents have been realized. Is MVC still a problem?

That is the whole content of this chapter. This article is a learning record and cannot be reproduced

Previous: Simple implementation of SpringMvc - IOC container and DI dependency injection
Next: Simple implementation of SpringMvc handwriting - MVC conclusion

A young idler, an old beggar.

Topics: Java Design Pattern Back-end