当前位置: 首页 > article >正文

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起步依赖
<!--AOP起步依赖-->
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.7.5</version>
</dependency>
  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 '操作日志表';

实体类

//操作日志实体类
@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; //操作耗时
}

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);

}
4.4.2 编码实现
  • 自定义注解@Log
/**
 * 自定义Log注解
 */
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
  • 修改业务实现类,在增删改业务方法上添加@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);
    }

    //省略其他代码...
}

以同样的方式,修改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;
    }

}

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

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

  • 添加一个新的部门

  • 数据表

在这里插入图片描述

在这里插入图片描述


http://www.kler.cn/news/133703.html

相关文章:

  • 【WiFI问题自助】解决WiFi能连上但是没有网的问题
  • 车载以太网-传输层-UDP
  • Flutter笔记:拖拽手势
  • java使用 TCP 的 Socket API 实现客户端服务器通信
  • NSSCTF第12页(3)
  • PS学习笔记——视图调整
  • em/px/rem/vh/vw 的区别?
  • Activiti,Apache camel,Netflex conductor对比,业务选型
  • Page分页records有数据,但是total=0,解决办法
  • 服务器数据恢复—VMware虚拟化下误操作导致服务器崩溃的数据恢复案例
  • 单片机FLASH下载算法的制作
  • vue-pdf在vue框架中的使用
  • 输出特殊图案,请在c环境中运行,看一看,Very Beautiful!
  • Vue 前置 后置 路由守卫 独享 路由权限控制 自定义属性
  • upload-labs(1-17关攻略详解)
  • Typora——优雅的排版也是一种品味
  • PHPhotoLibrary 获取相册权限注意事项
  • 「Verilog学习笔记」用3-8译码器实现全减器
  • 记录基于scapy构造ClientHello报文的尝试
  • 快速入门ESP32——开发环境配置PlatformIO IDE
  • Flutter NestedScrollView 、SliverAppBar全解析,悬浮菜单的应用
  • C#中的string和string builder有什么区别
  • docker 安装mongodb 实现 数据,日志,配置文件外挂
  • 关于我开始热爱生活,也会把该做的做好这件事
  • 【算法每日一练]-分块(保姆级教程 篇1)POJ3648
  • 百胜杯答题系统
  • 公网访问全能知识库工具AFFINE,Notion的免费开源替代
  • 【hive遇到的坑】—使用 is null / is not null 对string类型字段进行null值过滤无效
  • C++ 虚函数和多态性
  • React整理总结(三)