• SpringBoot 自定义注解 + SpringBoot Aop 实现切面日志处理


    思考为什么需要自定义注解和AOP

    ​ 项目中常常要打印日志,尤其是在做接口开发中,因为要面临着对前台数据的检查。

    ​ 这里我们使用的是自定义注解+AOP的方式实现日志

    简介

    自定义注解

    注解是一种能被添加到java代码中的元数据(python中的函数装饰器),类、方法、参数、变量和包都可以用注解来修饰。用来定义一个类、属性或者一些方法,以便程序能被捕译处理。相当于一个说明文件,告诉应用程序某个被注解的类或者属性是什么,要怎么处理。对被修饰的代码本身没有直接影响。

    Documented:

    ​ 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中

    Inherited :

    它指明被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中

    Target:
    • @Target(ElementType.TYPE) //接口、类、枚举、注解
    • @Target(ElementType.FIELD) //字段、枚举的常量
    • @Target(ElementType.METHOD) //方法 (一般都使用这个)
    • @Target(ElementType.PARAMETER) //方法参数
    • @Target(ElementType.CONSTRUCTOR) //构造函数
    • @Target(ElementType.LOCAL_VARIABLE)//局部变量
    • @Target(ElementType.ANNOTATION_TYPE)//注解
    • @Target(ElementType.PACKAGE) ///包
    Retention
    • RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略。
    • RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略。
    • RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

    SpringAOP简介

    • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
    • 连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
    • 切点(PointCut): 可以插入增强处理的连接点。
    • 切面(Aspect): 切面是通知和切点的结合。
    • 引入(Introduction):允许我们向现有的类添加新的方法或者属性。
    • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的代理对象。

    这里AOP不做过多介绍,没有基础的请先学习AOP知识

    自定义注解定义规则

    1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
    2. 参数成员只能用public或默认(default)这两个访问权修饰
    3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
    4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
    5. 注解也可以没有定义成员, 不过这样注解就没啥用了

    实战

    1.先导入springboot aop的包

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.编写自定义注解

    import java.lang.annotation.*;
    /**
     * 操作日志注解
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/5
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    //注解,目标在方法上
    @Target(ElementType.METHOD)
    public @interface LogOperation {
        /** 直接使用value那么使用注解的时候可以不用写value= */
        String value() default "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.编写切面处理类

    import com.alibaba.fastjson2.JSON;
    import com.sky.base.annotation.LogOperation;
    import com.sky.base.constant.DataStatus;
    import com.sky.base.security.pojo.LoginUser;
    import com.sky.model.SysLogOperation;
    import com.sky.service.SysLogOperationService;
    import com.sky.utils.IpUtils;
    import com.sky.utils.ServletUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    import java.time.LocalDateTime;
    import java.util.Objects;
    
    /**
     * 操作日志,切面处理类
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/5
     */
    @Aspect
    @Component
    @Slf4j
    public class LogOperationAspect {
    
        @Autowired
        private SysLogOperationService sysLogOperationService;
    
        /** 切入点 */
        @Pointcut("@annotation(com.sky.base.annotation.LogOperation)")
        public void logPointCut(){}
    
        /** 环绕通知处理 */
        @Around("logPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable{
            long beginTime = System.currentTimeMillis();
            try {
                // 调用目标方法,不写point.proceed(),方法不会调用,将结果返回
                Object result = point.proceed();
    
                // 计算执行时长
                long time = System.currentTimeMillis() - beginTime;
    
                // 保存日志
                saveLog(point,time, DataStatus.NORMAL);
    
                return result;
            } catch (Exception e) {
                // 计算执行时长(毫秒)
                long time = System.currentTimeMillis() - beginTime;
                // 保存日志
                saveLog(point,time, DataStatus.REMOVE);
                throw e;
            }
        }
    
        // 保存日志到数据库
        public void saveLog(ProceedingJoinPoint joinPoint, long time, Short status) throws Exception {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 通过反射获取目标方法
            Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),signature.getParameterTypes());
            // 获取方法上的注解
            LogOperation annotation = method.getAnnotation(LogOperation.class);
            // 获取操作日志对象
            Class<SysLogOperation> sysLogOperationClass = SysLogOperation.class;
            SysLogOperation sysLogOperation = sysLogOperationClass.newInstance();
            if (!Objects.isNull(annotation)){
                sysLogOperation.setOperation(annotation.value());
            }
            // 获取当前登录用户
            LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            
            sysLogOperation.setUsername(loginUser.getUser().getUsername());
            sysLogOperation.setRequestTime((int) time);
            sysLogOperation.setStatus(status);
            sysLogOperation.setCreateId(loginUser.getUser().getId());
            sysLogOperation.setUpdateId(loginUser.getUser().getId());
            sysLogOperation.setCreateTime(LocalDateTime.now());
            sysLogOperation.setUpdateTime(LocalDateTime.now());
            HttpServletRequest request = ServletUtils.getRequest();
            sysLogOperation.setIp(IpUtils.getIpAddr(request));
            sysLogOperation.setRequestUri(request.getRequestURI());
            sysLogOperation.setRequestMethod(request.getMethod());
    
            // 获取参数
            Object[] args = joinPoint.getArgs();
            String params = JSON.toJSONString(args[0]);
            sysLogOperation.setRequestParams(params);
    
            // 保存数据
            sysLogOperationService.save(sysLogOperation);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    4.将自定义注解写在连接点

    import com.sky.base.annotation.LogOperation;
    import com.sky.base.constant.Constants;
    import com.sky.utils.R;
    import com.sky.api.sys.param.request.LoginBody;
    import com.sky.base.security.service.SysLoginService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.validation.Valid;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/8/9
     */
    @Api(tags = "系统管理-登录")
    @RestController
    public class SysLoginController {
    
        @Autowired
        private SysLoginService sysLoginService;
    
        @LogOperation("登录")
        @ApiOperation("登录接口")
        @PostMapping("/login")
        public R login(@RequestBody @Valid LoginBody loginBody){
            // Todo 校验验证码
    
            String token = sysLoginService.login(loginBody.getUsername(), loginBody.getPassword());
            Map<String, Object> map = new HashMap<>();
            map.put(Constants.TOKEN,token);
            return R.ok(map,"登录成功");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    总结

    ​ 该博客是为了记录博主项目中遇到的日志实现,所以记录一下,大家可以看看自定义注解和切面类的写法,可能代码中存在一定问题,希望大家可以找出!

  • 相关阅读:
    应用程序服务器/事件驱动编程/CommonJS介绍
    编译,连接 -- 笔记 -2
    SQLi靶场
    ArduinoUNO实战-第一章-LED闪烁实验
    Hadoop
    现在的湖仓一体像是个伪命题
    CAPL如何对以太网报文的长度字段和校验和字段设置错误值
    element-ui:el-autocomplete实现滚动触底翻页
    DevExpress CMB下拉树
    python篇---python打印报错行
  • 原文地址:https://blog.csdn.net/weixin_46073538/article/details/126708903