JAVA-AOP Aspect-oriented programming Aspect tool class records input parameters, execution time, return parameters, etc. when a specific method is executed

Background: JAVA project, using AOP to slice specified functions. It can record the input parameters, execution time, return parameter results, etc. when a specific method is executed.


AOP can realize the data flow information flow in the entire process of function execution.
For example, before calling a function method, you need to call the external interface according to the header information to obtain the required information to determine the logic of subsequent method execution; after calling the function method, Recording of log information (request parameters, return results, execution duration, etc.).

1. Custom annotation class

You can add corresponding annotations to the functions you need to use, and AOP will not be applied to all functions.
For example, the name here is AutoLog. You only need to use @AutoLog on the corresponding required function.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解类,在对应的函数上使用@AutoLog即可
 */

@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
//@Documented //生成文档
public @interface AutoLog {
    
    
    String value() default "";
}

1.1 @Target

@Target explains the scope of objects modified by Annotation: Annotation can be used for packages, types (classes, interfaces, enumerations, Annotation types), type members (methods, constructors, member variables, enumeration values), method parameters and Local variables (such as loop variables, catch parameters). Using target in the declaration of the Annotation type can make the modified target clearer.
  Function: Used to describe the scope of use of annotations (ie: where the described annotations can be used)

The values ​​(ElementType) are:
    1. CONSTRUCTOR: used to describe the constructor
    2. FIELD: used to describe the domain
    3. LOCAL_VARIABLE: used to describe the local variable
    4. METHOD: used to describe the method
    5. PACKAGE: used to describe the package
    6 .PARAMETER: used to describe parameters
    7.TYPE: used to describe classes, interfaces (including annotation types) or enum declarations

1.2 @Retention

@Retention(RetentionPolicy.RUNTIME) annotations can be divided into 3 categories according to their life cycle:

1. RetentionPolicy.SOURCE: The annotation is only retained in the source file. When the Java file is compiled into a class file, the annotation is abandoned; 2.
RetentionPolicy.CLASS: The annotation is retained in the class file, but is abandoned when the jvm loads the class file. This It is the default life cycle;
3. RetentionPolicy.RUNTIME: the annotation is not only saved in the class file, but still exists after the jvm loads the class file;

These three life cycles correspond to: Java source file (.java file) —> .class file —> bytecode in memory.

So how to choose the appropriate annotation life cycle?

First of all, it is necessary to clarify the life cycle length SOURCE < CLASS < RUNTIME, so where the former can work, the latter must also work. Generally, if you need to dynamically obtain annotation information at runtime, you can only use RUNTIME annotations; if you want to perform some preprocessing operations at compile time, such as generating some auxiliary code (such as ButterKnife), use CLASS annotations; if you just do some checks For specific operations, such as @Override and @SuppressWarnings, you can use the SOURCE annotation.

2. Aspect cutting tool

2.1 JointPoint

JointPoint is an identifiable point during program running. This point can be used as the AOP entry point. The JointPoint object contains a lot of information related to the cut. Such as entry point objects, methods, properties, etc. We can obtain the status and information of these points through reflection, which is used for tracing and recording logging application information.

2.2 @Pointcut

Define the entry point through @Pointcut, which can be applied to all methods or specified methods
execution : used to match the connection point of method execution
@annotation : used to match the currently executing method holding the specified annotation method

//作用于service中所有方法
@Pointcut("execution(* com.demo.service.*(..))")

//作用与所有接口
@Pointcut("execution( public * com.demo.controller.*Controller.*(..))")

//作用与加了注解的指定接口,注解形式配置参考第1部分
@Pointcut("@annotation( com.demo.utils.AutoLog)")

2.3 Relevant insights in aspects

@Before defines a pre-notification, which executes a piece of logic before the intercepted method is executed.
@After is to execute a piece of logic after the intercepted method is executed.
@Around can execute a piece of logic before and after the intercepted method at the same time.
@AfterReturning allows you to obtain the return value of the execution method.
@AfterThrowing defines an exception notification. If the method throws an Exception type exception, the afterThrowing method will be called back.

Aspect tool classes are as follows:

import com.demo.services.AutoTestLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
@Slf4j
public class AutoTestLogAspect {
    
    
    long startTime;
    String body;

    @Autowired
    private AutoTestLogService autoTestLogService;

    //作用于所有接口
	//@Pointcut("execution( public * com.demo.controller.*Controller.*(..))")
    //定义切点 @Pointcut 在注解的位置切入代码 指定注解
    @Pointcut("@annotation( com.demo.utils.AutoLog)")

    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) {
    
    
    	//开始时间
        startTime = System.currentTimeMillis();
        //获取入参
        Object[] objects= joinPoint.getArgs();
       //将数组转成String
        body = Arrays.toString(objects);
    }

    @After("logPointCut()")
    public void doAfter() {
    
    
        long spendTime = System.currentTimeMillis() - startTime;
        log.info("spendTime:" + spendTime + "ms");
    }

    //切面 配置通知
    @AfterReturning(returning = "object", pointcut = "logPointCut()")
    public void doAfterReturning(Object object) {
    
    
        long spendTime = System.currentTimeMillis() - startTime;
        log.info("response={}", object.toString());
        //将被调用记录插入数据库
        autoTestLogService.insertAutoTestData(spendTime,true, object.toString());
    }

    @AfterThrowing(value = "logPointCut()", throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint, Exception exception){
    
    
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        long spendTime = System.currentTimeMillis() - startTime;
        if (requestAttributes != null) {
    
    
            HttpServletRequest request = requestAttributes.getRequest();
            //记录请求方法
            log.info("REQUEST: {}", request.getMethod());
            log.info("REQUEST_METHOD: {}", joinPoint.getSignature());
            //异常信息
            log.info("EXCEPTION: {}", exception.getMessage());
        }
    }

    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        long start = System.currentTimeMillis();
        try {
    
    
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
            log.error("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
            return result;
        } catch (Throwable e) {
    
    
            long end = System.currentTimeMillis();
            log.error("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
            throw e;
        }
    }
}

The main function is to record the information obtained by aspects, such as executing method parameters, passing parameters, returning parameters, etc.
service

import com.demo.dao.mapper.AutoTestLogMapper;
import com.demo.dao.po.AutoTestLog;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Date;

@Slf4j
@Service
public class AutoTestLogService {
    
    

    @Autowired
    AutoTestLogMapper autoTestLogMapper;

    /**
     * 埋点数据落表
     * @return
     */
    @Async("taskExecutor")
    public Boolean insertAutoTestData(double spendingTime, boolean isSuccess, Object object){
    
    
        Gson gson = new Gson();
        AutoTestLog autoTestLog = new AutoTestLog();
        autoTestLog.setSpendTime(spendingTime);
        autoTestLog.setSuccess(isSuccess);
        autoTestLog.setCreateDate(new Date());
        autoTestLog.setUpdatedDate(new Date());
        autoTestLog.setResponse(object.toString());
        try {
    
    
            autoTestLogMapper.insert(autoTestLog);
            log.info(String.format("AOP调用结果:%s", gson.toJson(autoTestLog)));
        }catch (Exception e){
    
    
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

}

DefinePO

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
@TableName("auto_log")
public class AutoTestLog implements Serializable {
    
    
    @TableId(type = IdType.AUTO)
    private Integer id;

    @TableField(value = "is_success")
    private boolean isSuccess;

    @TableField(value = "response")
    private String response;

    @TableField(value = "spend_time")
    private double spendTime;

	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+08:00")
    @TableField(value = "create_date",fill = FieldFill.INSERT)
    private Date createDate;
    
	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+08:00")
    @TableField(value = "updated_date",fill = FieldFill.UPDATE)
    private Date updatedDate;
}

mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.dao.po.AutoTestLog;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;

@Mapper
@Component
public interface AutoTestLogMapper extends BaseMapper<AutoTestLog> {
    
    
}

sql

DROP TABLE IF EXISTS `auto_log`;
CREATE TABLE `auto_log` (
  `id` int NOT NULL AUTO_INCREMENT,
  `is_success` tinyint DEFAULT NULL COMMENT '判断升级是否成功;0:失败;1:成功',
  `response` varchar(500) COMMENT '请求结果',
  `spend_time` DOUBLE(16,2) DEFAULT '0' COMMENT '接口耗时',
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
  INDEX (is_success)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

3. Call AOP in the same class

This annotation needs to be added to the startup class, which will inject the AnnotationAwareAspectJAutoProxyCreator component into the container, which is the core of AOP.

@EnableAspectJAutoProxy(exposeProxy=true)

The specific content of the service is as follows:
getservice() injects beans in order to solve transaction failures;
demo() is our aspect recording method;
exec() is the business function, in which the AOP method can be called in transaction mode.



    //解决事务失效
    private xxService getService(){
    
    
        return SpringUtil.getBean(this.getClass());
    }

    public exec{
    
    
        //需要执行的AOP函数
        getService.demo();
    }

    @AutoLog("synAutoTestLog")
    public String demo(){
    
    
        //业务代码
    }

4. Other obtainable parameters

Many parameters during the execution process can be obtained, such as:
parameters in pre-operations, such as request URL, request IP, request class name, request method name, request parameters, etc., return
value after execution, request time, browser type, operation System, header, etc.

package com.xkcoding.log.aop.aspectj;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import cn.hutool.json.JSONUtil;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Slf4j
public class AopLog {
    
    
    private static final String START_TIME = "request-start";

    /**
     * 切入点
     */
    @Pointcut("xxx")
    public void log() {
    
    

    }

    /**
     * 前置操作
     *
     * @param point 切入点
     */
    @Before("log()")
    public void beforeLog(JoinPoint point) {
    
    
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        log.info("请求 URL:{}", request.getRequestURL());
        log.info("请求 IP:{}", request.getRemoteAddr());
        log.info("请求类名:{},请求方法名:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
        log.info("body:{},", JSONUtil.toJsonStr(point.getArgs()));
        Map<String, String[]> parameterMap = request.getParameterMap();
        log.info("请求参数:{},", JSONUtil.toJsonStr(parameterMap));
        Long start = System.currentTimeMillis();
        request.setAttribute(START_TIME, start);
    }

    /**
     * 环绕操作
     *
     * @param point 切入点
     * @return 原方法返回值
     * @throws Throwable 异常信息
     */
    @Around("log()")
    public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
    
    
        Object result = point.proceed();
        log.info("返回值:{}", JSONUtil.toJsonStr(result));
        return result;
    }

    /**
     * 后置操作
     */
    @AfterReturning("log()")
    public void afterReturning() {
    
    
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        Long start = (Long) request.getAttribute(START_TIME);
        Long end = System.currentTimeMillis();
        log.info("请求耗时:{}毫秒", end - start);

        String header = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(header);
        log.info("浏览器类型:{},操作系统:{},原始User-Agent:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
    }
}

Reference:
https://blog.csdn.net/zhangting19921121/article/details/124351092
https://my.oschina.net/lizaizhong/blog/1611165
https://blog.csdn.net/weixin_44195615/article/details/ 113650701
https://blog.csdn.net/qq_18671415/article/details/111866546
https://blog.csdn.net/mameng1988/article/details/85548812
https://blog.csdn.net/Weixiaohuai/article/details /122494670

Guess you like

Origin blog.csdn.net/weixin_44436677/article/details/128694375