Javaweb之SpringBootWeb案例之AOP案例的详细解析

news2024/11/15 8:14:05

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>
</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.itheima.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.coloradmin.cn/o/1468157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

08 Redis之集群的搭建和复制原理+哨兵机制+CAP定理+Raft算法

5 Redis 集群 2.8版本之前, Redis采用主从集群模式. 实现了数据备份和读写分离 2.8版本之后, Redis采用Sentinel哨兵集群模式 , 实现了集群的高可用 5.1 主从集群搭建 首先, 基本所有系统 , “读” 的压力都大于 “写” 的压力 Redis 的主从集群是一个“一主多从”的读写分…

java+springmvc+springboot众筹救助系统mybatis

儿童众筹救助系统在流畅性&#xff0c;续航能力&#xff0c;等方方面面都有着很大的优势。这就意味着儿童众筹救助系统的设计可以比其他系统更为出色的能力&#xff0c;可以更高效的完成最新的救助基金、救助申请、众筹项目、捐赠信息等功能。 此系统设计主要采用的是JAVA语言来…

zemax凯尔纳目镜

也叫做凯涅尔目镜 凯尔纳目镜 是由单片透镜和双胶合透镜组成的。 一种改进型的冉斯登目镜&#xff0c;二片组成的接目镜及双凸透镜作为场镜。 它能校正倍率色差 &#xff0c;同时也减小了位置色差 、像散和畸变。 视场角大于40&#xff0c;可达50。 目前在一些中低倍望远镜中…

vscode怎么运行C++的cpp文件

非常简单&#xff0c;点击vscode导航栏的Extensions, 搜索C/C Compile Run安装上即可&#xff0c; 写好 C/C 程序之后&#xff0c;直接使用 F6 一键编译运行就能出结果。

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理 各位大佬&#xff0c;这是我的自学笔记&#xff0c;如有错误请指正&#xff0c;也欢迎在评论区学习交流&#xff0c;谢谢&#xff01; CUDA编程模型 我们使用CUDA_C语言进行CUDA编程&am…

成功解决No module named ‘skimage‘(ModuleNotFoundError)

成功解决No module named ‘skimage’(ModuleNotFoundError) &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您…

Day09-面向对象-多态

文章目录 Day09-面向对象-多态学习目标1对象数组1.1 对象数组的声明和使用1.2 对象数组的内存图分析 2. 多态2.1 前提2.2 多态的意义2.3 多态的特点2.3.1 成员方法的特点2.3.2 成员变量的特点 2.4 引用数据类型转换2.4.1 类型转换的意义2.4.2 转型的异常 3. final关键字的使用3…

PMP认证有什么用?含金量高吗?如何备考?

PMP备考多久能参加PMP考试&#xff0c;培训机构是关键点 依我这几年的持证体验来看&#xff0c;PMP认证的用处还是比较多的&#xff0c;也有一定的含金量&#xff0c;这两个方面基本都是随便一百度就能得到结果的&#xff0c;在考PMP的人群中唯一不同的可能就是备考方面的问题…

谷歌发布开源大模型 Gemma,评测+最佳微调实践来啦!

Gemma 是由 Google 推出的一系列轻量级、先进的开源模型&#xff0c;他们是基于 Google Gemini 模型的研究和技术而构建。它们是一系列text generation&#xff0c;decoder-only的大型语言模型&#xff0c;对英文的支持较好&#xff0c;具有模型权重开源、并提供预训练版本&…

探索 LRU 算法的缺陷与解决方案

LRU算法 Linux 的 Page Cache 和 MySQL 的 Buffer Pool 的大小是有限的&#xff0c;并不能无限的缓存数据&#xff0c;对于一些频繁访问的数据我们希望可以一直留在内存中&#xff0c;而一些很少访问的数据希望可以在某些时机可以淘汰掉&#xff0c;从而保证内存不会因为满了而…

【新手易错点】golang中byte和rune

1 总体区别 在Golang中&#xff0c;byte和rune是两种不同类型的数据。简单来说&#xff0c;byte是一个8位的无符号整数类型&#xff0c;而rune则是一个32位的Unicode字符类型。 Byte: 在Golang中&#xff0c;byte类型实际上是uint8的别名&#xff0c;它用来表示8位的无符号整…

神经网络系列---常用梯度下降算法

文章目录 常用梯度下降算法随机梯度下降&#xff08;Stochastic Gradient Descent&#xff0c;SGD&#xff09;&#xff1a;随机梯度下降数学公式&#xff1a;代码演示 批量梯度下降&#xff08;Batch Gradient Descent&#xff09;批量梯度下降数学公式&#xff1a;代码演示 小…

机器视觉选型:如何选择一个合适光源控制器

在机器视觉系统中&#xff0c;选择合适的光源及其控制器对于确保高质量图像捕获和处理至关重要。本文会提供一些建议&#xff0c;以便于引导您了解如何基于应用需求选择最合适的光源和光源控制器。 1. 理解光源的功率需求 不同类型的光源具有不同的功率需求&#xff0c;这直接…

Linux:gcc的基本知识

gcc 是一个将C语言文件变成可执行文件的工具。 在Linux中&#xff0c;如果需要将一个C语言文件变得可以执行&#xff0c;那么除了这个文件本身的内容是C语言编写的内容外&#xff0c;还需要gcc这个编译工具进行编译才行。 gcc 使用的格式方法:gcc 要编译的文件 //在该代码下…

Jenkins常规配置(0)

Jenkins常规配置 20211005 1、配置中文 需要先安装插件&#xff0c;然后进入到全局配置中设置&#xff1a;zh_CN 2、Jenkins访问地址 安装的时候&#xff0c;会让设置访问路径&#xff0c;这里可以进行修改&#xff0c;进入配置全局变量中设置 3、全局配置go环境&#xff0…

二级等保需要什么样的SSL证书?

根据等级保护对象在国家安全、经济建设、社会生活中的重要程度&#xff0c;以及一旦遭到破坏、丧失功能或者数据被篡改、泄露、丢失、损毁后&#xff0c;对国家安全、社会秩序、公共利益以及公民&#xff0c;法人和其他组织的合法权益的侵害程度等因素&#xff0c;等级保护对象…

js 文件预览 在窗口设置“自定义名称”

1. 最近需要做一个点击表格某一列的标题&#xff0c;预览当前文件的一个小功能。本身功能很简单&#xff0c;点击该标题&#xff0c;预览文件&#xff0c;那么拿到他对应的文件地址&#xff0c;在浏览器打开就行了。 2. 事实如此&#xff0c;使用window.open(url, _blank);就行…

Flink/flinksql 语法 窗口与join 一文全 相关概念api汇总总结,底层process算子总结,与数据延迟处理,超时场景解决方案

Flink 窗口概念与join汇总总结 1 SQL语法中窗口语法相关&#xff08;仅仅是flinksql中 窗口的语法&#xff09;1.1 sql窗口1.2 window topN 2 java/SQL join语法与介绍2.1 有界join2.1.1 Window Join2.1.2 Interval Join2.1.3 Temporary Join2.1.4 LoopUp Join2.2 无界join2.2.…

基于协同过滤算法的体育商品推荐系统

摘要 本文深入探讨了基于协同过滤算法的体育商品推荐系统的构建方法及其在电子商务中的重要性。首先&#xff0c;介绍了协同过滤算法的基本原理&#xff0c;包括用户-商品矩阵、相似度度量和推荐生成。其次&#xff0c;探讨了协同过滤算法在体育商品推荐中的两种主要应用方式&a…

OpenCV 4基础篇| OpenCV像素的编辑

目录 1. 前言1. 像素的访问1.1 数组索引访问1.2 img.item() 2. 像素的修改2.1 数值索引修改2.2 img.itemset() 1. 前言 像素是构成数字图像的基本单位&#xff0c;像素处理是图像处理的基本操作。 对像素的访问、修改&#xff0c;可以使用 Numpy 方法直接访问数组元素。 1. 像…