• SpringBoot-AOP学习案例


    4. AOP案例

    SpringAOP的相关知识我们就已经全部学习完毕了。最后我们要通过一个案例来对AOP进行一个综合的应用。

    4.1 需求

    需求:将案例中增、删、改相关接口的操作日志记录到数据库表中

    • 就是当访问部门管理和员工管理当中的增、删、改相关功能接口时,需要详细的操作日志,并保存在数据表中,便于后期数据追踪。

    操作日志信息包含:

    • 操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

    所记录的日志信息包括当前接口的操作人是谁操作的,什么时间点操作的,以及访问的是哪个类当中的哪个方法,在访问这个方法的时候传入进来的参数是什么,访问这个方法最终拿到的返回值是什么,以及整个接口方法的运行时长是多长时间。

    4.2 分析

    问题1:项目当中增删改相关的方法是不是有很多?

    • 很多

    问题2:我们需要针对每一个功能接口方法进行修改,在每一个功能接口当中都来记录这些操作日志吗?

    • 这种做法比较繁琐

    以上两个问题的解决方案:可以使用AOP解决(每一个增删改功能接口中要实现的记录操作日志的逻辑代码是相同)。

    可以把这部分记录操作日志的通用的、重复性的逻辑代码抽取出来定义在一个通知方法当中,我们通过AOP面向切面编程的方式,在不改动原始功能的基础上来对原始的功能进行增强。目前我们所增强的功能就是来记录操作日志,所以也可以使用AOP的技术来实现。使用AOP的技术来实现也是最为简单,最为方便的。

    问题3:既然要基于AOP面向切面编程的方式来完成的功能,那么我们要使用 AOP五种通知类型当中的哪种通知类型?

    • 答案:环绕通知

    所记录的操作日志当中包括:操作人、操作时间,访问的是哪个类、哪个方法、方法运行时参数、方法的返回值、方法的运行时长。

    方法返回值,是在原始方法执行后才能获取到的。

    方法的运行时长,需要原始方法运行之前记录开始时间,原始方法运行之后记录结束时间。通过计算获得方法的执行耗时。

    基于以上的分析我们确定要使用Around环绕通知。

    问题4:最后一个问题,切入点表达式我们该怎么写?

    • 答案:使用annotation来描述表达式

    要匹配业务接口当中所有的增删改的方法,而增删改方法在命名上没有共同的前缀或后缀。此时如果使用execution切入点表达式也可以,但是会比较繁琐。 当遇到增删改的方法名没有规律时,就可以使用 annotation切入点表达式

    4.3 步骤

    简单分析了一下大概的实现思路后,接下来我们就要来完成案例了。案例的实现步骤其实就两步:

    • 准备工作
      1. 引入AOP的起步依赖
      2. 导入资料中准备好的数据库表结构,并引入对应的实体类
    • 编码实现
      1. 自定义注解@Log
      2. 定义切面类,完成记录操作日志的逻辑

    4.4 实现

    4.4.1 准备工作
    1. AOP起步依赖
    
    <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-aopartifactId>
                <version>2.7.5version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 导入资料中准备好的数据库表结构,并引入对应的实体类

    数据表

    -- 操作日志表
    create table operate_log(
        id int unsigned primary key auto_increment comment 'ID',
        operate_user int unsigned comment '操作人',
        operate_time datetime comment '操作时间',
        class_name varchar(100) comment '操作的类名',
        method_name varchar(100) comment '操作的方法名',
        method_params varchar(1000) comment '方法参数',
        return_value varchar(2000) comment '返回值',
        cost_time bigint comment '方法执行耗时, 单位:ms'
    ) comment '操作日志表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    实体类

    //操作日志实体类
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class OperateLog {
        private Integer id; //主键ID
        private Integer operateUser; //操作人ID
        private LocalDateTime operateTime; //操作时间
        private String className; //操作类名
        private String methodName; //操作方法名
        private String methodParams; //操作方法参数
        private String returnValue; //操作方法返回值
        private Long costTime; //操作耗时
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Mapper接口

    @Mapper
    public interface OperateLogMapper {
    
        //插入日志数据
        @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
                "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
        public void insert(OperateLog log);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    4.4.2 编码实现
    • 自定义注解@Log
    /**
     * 自定义Log注解
     */
    @Target({ElementType.METHOD})
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 修改业务实现类,在增删改业务方法上添加@Log注解
    @Slf4j
    @Service
    public class EmpServiceImpl implements EmpService {
        @Autowired
        private EmpMapper empMapper;
    
        @Override
        @Log
        public void update(Emp emp) {
            emp.setUpdateTime(LocalDateTime.now()); //更新修改时间为当前时间
    
            empMapper.update(emp);
        }
    
        @Override
        @Log
        public void save(Emp emp) {
            //补全数据
            emp.setCreateTime(LocalDateTime.now());
            emp.setUpdateTime(LocalDateTime.now());
            //调用添加方法
            empMapper.insert(emp);
        }
    
        @Override
        @Log
        public void delete(List<Integer> ids) {
            empMapper.delete(ids);
        }
    
        //省略其他代码...
    }
    
    • 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

    以同样的方式,修改EmpServiceImpl业务类

    • 定义切面类,完成记录操作日志的逻辑
    @Slf4j
    @Component
    @Aspect //切面类
    public class LogAspect {
    
        @Autowired
        private HttpServletRequest request;
    
        @Autowired
        private OperateLogMapper operateLogMapper;
    
        @Around("@annotation(com.shisan.anno.Log)")
        public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
            //操作人ID - 当前登录员工ID
            //获取请求头中的jwt令牌, 解析令牌
            String jwt = request.getHeader("token");
            Claims claims = JwtUtils.parseJWT(jwt);
            Integer operateUser = (Integer) claims.get("id");
    
            //操作时间
            LocalDateTime operateTime = LocalDateTime.now();
    
            //操作类名
            String className = joinPoint.getTarget().getClass().getName();
    
            //操作方法名
            String methodName = joinPoint.getSignature().getName();
    
            //操作方法参数
            Object[] args = joinPoint.getArgs();
            String methodParams = Arrays.toString(args);
    
            long begin = System.currentTimeMillis();
            //调用原始目标方法运行
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
    
            //方法返回值
            String returnValue = JSONObject.toJSONString(result);
    
            //操作耗时
            Long costTime = end - begin;
    
    
            //记录操作日志
            OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
            operateLogMapper.insert(operateLog);
    
            log.info("AOP记录操作日志: {}" , operateLog);
    
            return result;
        }
    
    }
    
    • 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

    代码实现细节: 获取request对象,从请求头中获取到jwt令牌,解析令牌获取出当前用户的id。

    重启SpringBoot服务,测试操作日志记录功能:

    • 添加一个新的部门

    • 数据表

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    雷达有源干扰识别仿真
    “轻松实现文件夹批量重命名:使用顺序编号批量改名“
    vue 封装一个Dialog组件
    Python的PyQt框架的使用-创建主窗体篇
    30天刷题计划(四)
    【毕业设计】深度学习动物识别系统 - python 卷积神经网络 机器视觉
    fatal: Not a git repository (or any parent up to mount point /home)解决方法
    pycharm2020无法打开,点击无反应
    Win10 屏蔽键盘按键
    如何快速配置NFS
  • 原文地址:https://blog.csdn.net/apple_51927114/article/details/134493053