SpringBoot uses AOP to record logs

Why use AOP?

The answer is decoupling !

Aspect Oriented Programming Aspect Oriented Programming. Decoupling is what programmers have been pursuing in the process of coding development. AOP is also born for decoupling.

The specific idea is: define an aspect, define the processing method in the longitudinal direction of the aspect, and return to the horizontal business flow after the processing is completed.

AOP is mainly realized by the technology of proxy mode. The specific agent implementation can refer to this article, which is very detailed. https://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html

A technology that realizes the unified maintenance of program functions through pre-compilation and runtime dynamic agents. The use of AOP can isolate the various parts of the business logic, thereby reducing the coupling between the various parts of the business logic, improving the reusability of the program, and improving the efficiency of development.


Common work scenarios

  1. Transaction control

  2. Logging

This article does not have excessive deep learning principles, because I am a rookie, first learn how not to work overtime.

Must-know concepts

AOP related terms

Advice

The notification describes the work to be done by the aspect and when it will be performed. For example, our log section needs to record the duration of each interface call, and we need to record the current time before and after the interface call, and then take the difference.

  • Before notification (Before): Call the notification function before the target method is called;

  • After notification (After): Call the notification function after the target method is called, and don't care about the return result of the method;

  • Return notification (AfterReturning): call the notification function after the target method is successfully executed;

  • Exception notification (AfterThrowing): call the notification function after the target method throws an exception;

  • Around notification (Around): The notification wraps the target method and executes custom behavior before and after the target method is called.

Connection point (JoinPoint)

When the notification function is applied. For example, when the interface method is called, it is the connection point of the log aspect.

Pointcut

The pointcut defines the scope to which the notification function is applied. For example, the application scope of the log aspect is all interfaces, that is, all interface methods of the controller layer.

Aspect (Aspect)

Aspect is the combination of notification and pointcut, which defines when and where to apply the notification function.

Introduction

Without modifying the existing class, add new methods or properties to the existing class.

Weaving

The process of applying aspects to target objects and creating new proxy objects.

Use annotations to create aspects in Spring

Related notes

  • @Aspect: used to define the aspect

  • @Before: The notification method will be executed before the target method is called

  • @After: The notification method will be executed after the target method returns or throws an exception

  • @AfterReturning: The notification method will be executed after the target method returns

  • @AfterThrowing: The notification method will be executed after the target method throws an exception

  • @Around: The notification method encapsulates the target method

  • @Pointcut: define pointcut expression

Cutpoint expression

Specifies the scope to which the notification is applied, the expression format:

execution
(方法修饰符
 
返回类型
 
方法所属的包.类名.方法名称(方法参数)
//com.ninesky.study.tiny.controller包中所有类的public方法都应用切面里的通知
execution(public * com.ninesky.study.tiny.controller.*.*(..))
//com.ninesky.study.tiny.service包及其子包下所有类中的所有方法都应用切面里的通知
execution(* com.ninesky.study.tiny.service..*.*(..))
//com.ninesky.study.tiny.service.PmsBrandService类中的所有方法都应用切面里的通知
execution(* com.macro.ninesky.study.service.PmsBrandService.*(..))

Practical application-use AOP to record logs

Switching from a traditional industry, I have never thought about logging and burying points before. For the first job, it is really important to choose a good platform.

Define the log information package

Used to encapsulate the log information that needs to be recorded, including the description of the operation, time, consumption time, url, request parameters and return results, etc.

public class WebLog {
    /**
     * 操作描述
     */
    private String description;
    /**
     * 操作用户
     */
    private String username;
    /**
     * 操作时间
     */
    private Long startTime;
    /**
     * 消耗时间
     */
    private Integer spendTime;
    /**
     * 根路径
     */
    private String basePath;
    /**
     * URI
     */
    private String uri;
    /**
     * URL
     */
    private String url;
    /**
     * 请求类型
     */
    private String method;
    /**
     * IP地址
     */
    private String ip;
    /**
     * 请求参数
     */
    private Object parameter;
    /**
     * 请求返回的结果
     */
    private Object result;
    //省略了getter,setter方法
}

Define annotations and reduce the amount of code through annotations

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperationLog {
    String name();//调用接口的名称

    boolean intoDb() default false;//该条操作日志是否需要持久化存储
}

Unified log processing aspect

@Aspect
@Component
@Order(1)
@Slf4j
public class WebLogAspect {
    private static final Logger controlLog = LoggerFactory.getLogger("tmall_control");
    @Pointcut("execution(public * com.yee.walnut.*.*.*(..))")
    public void webLog() {
    }

    @Before(value = "webLog()&& @annotation(OperationLog)")
    public void doBefore(ControllerWebLog controllerWebLog) throws Throwable {
    }

    @AfterReturning(value = "webLog()&& @annotation(OperationLog)", returning = "ret")
    public void doAfterReturning(Object ret, ControllerWebLog controllerWebLog) throws Throwable {
    }

    @Around(value = "webLog()&& @annotation(OperationLog)")
    public Object doAround(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        Object[] objs = joinPoint.getArgs();
        WebLog webLog = new WebLog();
        Object result = joinPoint.proceed();//返回的结果,这是一个进入方法和退出方法的一个分界
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteUser());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, joinPoint.getArgs()));
        webLog.setResult(JSONUtil.parse(result));
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        controlLog.info("RequestAndResponse {}", JSONObject.toJSONString(webLog));
        //必须有这个返回值。可以这样理解,Around方法之后,不再是被织入的函数返回值,而是Around函数返回值
        return result;
    }


    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            } else {
                argList.add(args[i]);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}

Add a custom annotation to the method

@OperationLog(name = "TurnOnOffStrategy")
public String doOperation(GlobalDto globalDto, DeviceOperator deviceOperator) {
}

Guess you like

Origin blog.csdn.net/jianzhang11/article/details/106110691