Detailed explanation of SpringBoot Aop and various usage scenarios

Posted by karq on Sun, 02 Jan 2022 04:37:52 +0100

preface

AOP aspect oriented programming is a very important idea in programming. This article mainly introduces the use and cases of SpringBoot aspect AOP

What is aop

AOP (aspect oriented programming): aspect oriented programming, aspect oriented programming (also known as aspect oriented programming), is a hot spot in software development and an important content in Spring framework. AOP can isolate each part of business logic, reduce the coupling between each part of business logic, improve the reusability of program, and improve the efficiency of development.

Usage scenario

AOP can isolate our edge services and reduce the logical coupling of unrelated services. Improve the reusability of the program and improve the efficiency of development. It is generally used for logging, performance statistics, security control, permission management, transaction processing, exception handling and resource pool management. Usage scenario

Why do I need aspect oriented programming

The advantages and disadvantages of object-oriented programming (OOP) are obvious. When it is necessary to add a common method for multiple objects without inheritance relationship, such as logging, performance monitoring, etc., if the object-oriented programming method is adopted, the same method needs to be added to each object, resulting in a large amount of repetitive workload and a large amount of repetitive code, which is not conducive to maintenance. Aspect oriented programming (AOP) is a supplement to object-oriented programming. In short, it is a programming idea to uniformly deal with a "aspect" problem. If you use AOP to record and process logs, all log codes are concentrated in one place, and you don't need to add them in each method, which greatly reduces duplicate codes.

Technical points

  1. Advice contains the crosscutting behavior that needs to be used for multiple application objects. It doesn't matter if you don't understand it at all. To put it more simply, it defines "when" and "what to do".

  2. Join points are all points where notifications can be applied during program execution.

  3. Pointcut defines where to cut in and which connection points will be notified. Obviously, the tangent point must be the connection point.

  4. Aspect is a combination of notification and pointcut. Together, notifications and pointcuts define the entire content of the aspect - what, when, and where to complete the function.

  5. Introduction allows us to add new methods or properties to existing classes.

  6. Weaving is the process of applying the aspect to the target object and creating a new proxy object. It is divided into compilation weaving, class loading weaving and runtime weaving.

Integrated use

Import dependency

Using aop in spring boot requires aop dependencies

 <!--aop section-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

Note that the version here depends on the spring boot dependencies in the spring boot start parent pom

Write intercepted bean s

Here we define a controller to intercept the records of all requests

@RestController
public class AopController {

    @RequestMapping("/hello")
    public String sayHello(){
        System.out.println("hello");
        return "hello";
    }
}

Define section

When using facets, SpringBoot annotates POJOs with the @ Aspect annotation, which indicates that the class is not only a POJO, but also a facet container

Define tangent point

Pointcut is defined by @ pointcut annotation and pointcut expression.

@The Pointcut annotation can define reusable pointcuts within an aspect.

Because the minimum granularity of Spring aspect is to reach the method level, and the execution expression can be used to explicitly specify method related components such as method return type, class name, method name and parameter name, and in practice, most business scenarios that need to use AOP only need to reach the method level, so the execution expression is most widely used. The following figure shows the syntax of the execution expression:

execution indicates that it is triggered when the method is executed. Starting with '' indicates that the return value type of the method is any type. Then the fully qualified class name and method name, and "" can represent any class and any method. For the method parameter list, you can use "..." to indicate that the parameter is of any type. If you need more than one expression, you can use "& &", "|" and "!" Complete operations with, or, and not.

Define notification

There are five types of notifications:

  1. Pre notification (@Before): calling notification before target method invocation
  2. Post notification (@After): call notification after completion of the target method.
  3. Surround notification (@ Around): executes custom methods before and after the notified method is called
  4. Return notification (@AfterReturning): call notification after successful execution of the target method.
  5. Exception notification (@AfterThrowing): calling notification after the target method throws an exception.

Three types of notifications are defined in the code. The @ before annotation is used to identify the pre notification, print "before advice...", the @ after annotation is used to identify the post notification, print "after advice...", and the @ Around annotation is used to identify the surround notification. Before and after the method is executed, print "before" and "after" respectively. Such a section is defined, and the code is as follows:

@Aspect
@Component
public class AopAdvice {

    @Pointcut("execution (* com.shangguan.aop.controller.*.*(..))")
    public void test() {

    }

    @Before("test()")
    public void beforeAdvice() {
        System.out.println("beforeAdvice...");
    }

    @After("test()")
    public void afterAdvice() {
        System.out.println("afterAdvice...");
    }

    @Around("test()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.out.println("after");
    }

}

Operation results

Case scenario

Here, we use the Aop aspect completely through a logging scenario. The business layer only needs to care about the code logic implementation rather than the logging of request parameters and response parameters

First, we need to customize a facet class GlobalLogAspect for global logging

Then add @ Aspect annotation to this class, define a public Pointcut to point to the package to be processed, and then define a pre notification (add @ Before annotation), post notification (add @ AfterReturning) and surround notification (add @ Around) methods

Log information class

package cn.soboys.core;

import lombok.Data;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 18:48
 * log information
 */
@Data
public class LogSubject {
    /**
     * pedagogical operation
     */
    private String description;

    /**
     * Operating user
     */
    private String username;

    /**
     * Operation time
     */
    private String startTime;

    /**
     * Time consuming
     */
    private String spendTime;

    /**
     * URL
     */
    private String url;

    /**
     * Request type
     */
    private String method;

    /**
     * IP address
     */
    private String ip;

    /**
     * Request parameters
     */
    private Object parameter;

    /**
     * The result returned by the request
     */
    private Object result;

    /**
     * city
     */
    private String city;

    /**
     * Request device information
     */
    private String device;



}


Global log interception

package cn.soboys.core;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 14:52
 * section
 */
public class BaseAspectSupport {
    public Method resolveMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();

        Method method = getDeclaredMethod(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("The target method could not be resolved: " + signature.getMethod().getName());
        }
        return method;
    }

    private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }
        return null;
    }
}

GlobalLogAspect class

package cn.soboys.core;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import cn.soboys.core.utils.HttpContextUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 15:22
 * Global logger
 */
@Slf4j
@Aspect
@Component
public class GlobalLogAspect extends BaseAspectSupport {
    /**
     * Define tangent Pointcut
     */
    @Pointcut("execution(public * cn.soboys.mallapi.controller.*.*(..))")
    public void log() {

    }


    /**
     * Around Advice 
     *
     * @param joinPoint
     * @return
     */
    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        LogSubject logSubject = new LogSubject();
        //Recording time timer
        TimeInterval timer = DateUtil.timer(true);
        //results of enforcement
        Object result = joinPoint.proceed();
        logSubject.setResult(result);
        //Execution time
        String endTime = timer.intervalPretty();
        logSubject.setSpendTime(endTime);
        //Execution parameters
        Method method = resolveMethod(joinPoint);
        logSubject.setParameter(getParameter(method, joinPoint.getArgs()));

        HttpServletRequest request = HttpContextUtil.getRequest();
        // Interface request time
        logSubject.setStartTime(DateUtil.now());
        //Request link
        logSubject.setUrl(request.getRequestURL().toString());
        //Request methods: GET,POST, etc
        logSubject.setMethod(request.getMethod());
        //Request device information
        logSubject.setDevice(HttpContextUtil.getDevice());
        //Request address
        logSubject.setIp(HttpContextUtil.getIpAddr());
        //Interface description
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            logSubject.setDescription(apiOperation.value());
        }

        String a = JSONUtil.toJsonPrettyStr(logSubject);
        log.info(a);
        return result;

    }


    /**
     * Get the request parameters according to the method and the passed in parameters
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            //Take the parameter modified by the RequestBody annotation as the request parameter
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            //Take the parameter modified by the RequestParam annotation as the request parameter
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            String key = parameters[i].getName();
            if (requestBody != null) {
                argList.add(args[i]);
            } else if (requestParam != null) {
                map.put(key, args[i]);
            } else {
                map.put(key, args[i]);
            }
        }
        if (map.size() > 0) {
            argList.add(map);
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}

Topics: Spring Boot