使用AOP切面实现日志记录功能

系列文章

1.SpringBoot整合RabbitMQ并实现消息发送与接收
2. 解析JSON格式参数 & 修改对象的key
3. VUE整合Echarts实现简单的数据可视化
4. Java中运用BigDecimal对字符串的数值进行加减乘除等操作
5. List<HashMap<String,String>>实现自定义字符串排序(key排序、Value排序)

更多该系列文章可以看我主页哦


目录

  • 系列文章
  • 前言
    • 一、准备工作
    • 二、准备实操
      • 2.1、编写一个自己定义的Log注解
      • 2.2、编写切面类LogAspect.java
          • 2.2.1、定义切面
          • 2.2.2、代码编写
      • 总结一下
      • 源码展示


前言

说到AOP大家都可以想到他是面向切面的编程,它通过将横切关注点(例如日志记录、事务管理、权限控制等)从主要业务逻辑中分离出来,以模块化的方式进行管理。在AOP中,通过定义切面(Aspect)来捕获和处理横切关注点,然后将其应用于特定的目标对象或方法。

官方的解释有点抽象,我们举个例子说明:假设我们需要在多个方法中添加日志记录功能。传统的方式是在每个方法中都添加日志代码,但这样会导致代码重复,并且当我们需要修改日志记录逻辑时,需要逐个修改所有方法。而使用AOP,我们只需定义一个切面,将日志记录的逻辑写在切面中。然后,通过在需要添加日志的地方进行配置,就能自动将切面应用到目标方法中,实现日志记录的功能。

文章说明

本篇文章主要是使用Aop的环绕通知去实现将每次请求的接口信息(操作的模块,请求方法,请求的url,请求的ip,入参,出参,以及耗时)进行记录并存到数据库。

一、准备工作

首先我们导入Aop的坐标

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

因为我们有一些结果要json输出 ,所以用了fastjson依赖,下面给出xml坐标,当然你也可以喜欢着其他的转json工具

        <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.3</version></dependency>

二、准备实操

2.1、编写一个自己定义的Log注解

在这里插入图片描述

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {// 业务类型BusinessType businessType() default BusinessType.OTHER;// 模块名称String title() default "";
}

模块名称、业务类型等可以根据自己的实际情况去添加和删除
之后我们将注解写在需要记录的方法上面,这里是一个简单的分页查询 ,入参为每页条数和页码,出参就是分页的结果

    @Log(title = "分页查询商品",businessType = BusinessType.GETAll)@GetMapping("/goods/list")public Result pageList(int pageNum,int pageSize,String name , String useage){return Result.success(goodsService.selectPage(pageNum,pageSize,name,useage));}

2.2、编写切面类LogAspect.java

2.2.1、定义切面
    /*** 定义切面*/@Pointcut("@annotation(com.example.masks.annotation.Log)")public void pt() {}
2.2.2、代码编写

我们定义一个环绕切点,首先记录当前时间,作为切点方法执行前的时间戳,使用 pjp.proceed() 执行切点方法,之后接着计算切点方法执行的时长,并记录日志。这里调用了 handleLog() 方法来处理日志记录,它需要传入 pjp、runTime 和 result 三个参数。

    /*** 环绕切点* @param* @return result*/@Around("Log()")public Object log(ProceedingJoinPoint pjp) throws Throwable {long beginTime = System.currentTimeMillis();// 执行切点方法Object result = pjp.proceed();// 执行时长Long runTime = System.currentTimeMillis() - beginTime;handleLog(pjp,runTime,result);return result;}

handleLog() 方法中,首先获取切点方法的签名和注解信息,在从注解中获取模块和业务类型信息, 之后依次获取、请求参数HTTP方法IP地址请求URL 等信息:

    private void handleLog(ProceedingJoinPoint pjp,Long runTime, Object result) {MethodSignature signature = (MethodSignature) 		    pjp.getSignature();Method method = signature.getMethod();// 获取注解内容Log logAnnotation = method.getAnnotation(Log.class);// 获取模块String title = logAnnotation.title();// 获取业务类型BusinessType businessType = logAnnotation.businessType();Object[] args = pjp.getArgs();// 入参数String params = JSON.toJSONString(args);//出参String res = JSON.toJSONString(result);// 请求方法String httpMenthod = httpServletRequest.getMethod();// ipString ip = IPUtils.getIpAddr(httpServletRequest);// 请求urlString requestURL = httpServletRequest.getRequestURL().toString();// 封装日志对象SysLog sysLog = new SysLog(title, businessType, httpMenthod, requestURL, ip, params, res, runTime);// 这里可以根据自己的需求去处理sysLog,可以存储到数据库等,储存到数据库的操作就不展示了,比较简单,我这里就控制台输出一下这一条信息System.out.println(sysLog);

展示一下:因为我把日志存储到了数据库、就给大家展示一下数据库的结果
在这里插入图片描述

总结一下

总的来说,AOP 日志记录是一种实现代码模块化和复用的好方法,可以提高代码的可维护性和可读性。在实际开发中,我们应该灵活运用 AOP 技术,根据实际需求选择合适的切点表达式和日志记录方式,并注意日志级别和格式的设置,以便更好地记录和分析日志信息。

希望通过本篇文章,让大家对Aop有一个更深入的了解,尤其是AOP去处理日志的功能,是Aop最常见的一个功能,我这里只是进行简单的AOP日志功能的运用,如果大家有什么更好的方法和对我代码改进的地方,请大家积极私信,一起努力

源码展示

sysLog.java 封装的实体

public class SysLog {private Long id;/*** 操作模块*/private String title;/*** 业务类型*/private BusinessType businessType;/*** 请求类型*/private String requestMethod;/*** 请求URl*/private String operUrl;/*** 请求IP*/private String operIp;/*** 请求参数*/private String operParam;/*** 出参*/private String resultParam;/*** 消耗时间-ms*/private Long costTime;public SysLog(String title, BusinessType businessType, String requestMethod, String operUrl, String operIp, String operParam,String resultParam,  Long costTime) {this.title = title;this.businessType = businessType;this.requestMethod = requestMethod;this.operUrl = operUrl;this.operIp = operIp;this.operParam = operParam;this.resultParam = resultParam;this.costTime = costTime;}

Log注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {// 业务类型BusinessType businessType() default BusinessType.OTHER;// 模块名称String title() default "";
}

LogAspect.java 切面类

@Aspect
@Component
public class LogAspect {@AutowiredHttpServletRequest httpServletRequest;private static final Logger log = LoggerFactory.getLogger(LogAspect.class);/*** 定义切面*/@Pointcut("@annotation(com.xiaoke.annotation.Log)")public void pt() {}/*** 环绕切点*/@Around("pt()")public Object log(ProceedingJoinPoint pjp) throws Throwable {long beginTime = System.currentTimeMillis();// 执行切点方法MObject result = pjp.proceed();// 执行时长Long runTime = System.currentTimeMillis() - beginTime;// 记录日志handleLog(pjp,runTime,result);return result;}private void handleLog(ProceedingJoinPoint pjp,Long runTime, Object result) {MethodSignature signature = (MethodSignature) 		    pjp.getSignature();Method method = signature.getMethod();// 获取注解内容Log logAnnotation = method.getAnnotation(Log.class);// 获取模块String title = logAnnotation.title();// 获取业务类型BusinessType businessType = logAnnotation.businessType();Object[] args = pjp.getArgs();// 入参数String params = JSON.toJSONString(args);//出参String res = JSON.toJSONString(result);// 请求方法String httpMenthod = httpServletRequest.getMethod();// ipString ip = IPUtils.getIpAddr(httpServletRequest);// 请求urlString requestURL = httpServletRequest.getRequestURL().toString();// 封装日志对象SysLog sysLog = new SysLog(title, businessType, httpMenthod, requestURL, ip, params, res, runTime);// 这里可以根据自己的需求去处理sysLog,可以存储到数据库等,储存到数据库的操作就不展示了,比较简单,我这里就控制台输出一下这一条信息System.out.println(sysLog);}
}

下面是俩个工具类,百度可以搜索到 这里也给出源码

BusinessType.java 这是一个枚举

	/*** @Description 业务操作类型*/
public enum BusinessType {/*** 其它*/OTHER,/*** 新增*/INSERT,/*** 修改*/UPDATE,/*** 删除*/DELETE,/*** 授权*/GRANT,
}

IPutils.java 这个主要是获取ip

import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;public class IPUtils {private static final String IP_UTILS_FLAG = ",";private static final String UNKNOWN = "unknown";private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";private static final String LOCALHOST_IP1 = "127.0.0.1";/*** 获取IP地址* <p>* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = null;try {//以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。ip = request.getHeader("X-Original-Forwarded-For");if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("X-Forwarded-For");}//获取nginx等代理的ipif (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("x-forwarded-for");}if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}//兼容k8s集群获取ipif (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {//根据网卡取本机配置的IPInetAddress iNet = null;try {iNet = InetAddress.getLocalHost();} catch (UnknownHostException e) {System.out.println();System.out.println("getClientIp error"+e.getMessage());}assert iNet != null;ip = iNet.getHostAddress();}}} catch (Exception e) {System.out.println("IPUtils ERROR"+e.getMessage());}//使用代理,则获取第一个IP地址if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));}return ip;}
}

以上就是全部源码了 有兴趣的朋友可以观看我其他的文章和私信我哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/119481.shtml

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

相关文章

密码学-SHA-1算法

实验七 SHA-1 一、实验目的 熟悉SHA-1算法的运行过程&#xff0c;能够使用C语言编写实现SHA-1算法程序&#xff0c;增 加对摘要函数的理解。 二、实验要求 (1)理解SHA-1轮函数的定义和工作过程。 (2)利用VC语言实现SHA- 1算法。 (3)分析SHA- 1算法运行的性能。 三、实验…

【深蓝学院】手写VIO第8章--相机与IMU时间戳同步--笔记

0. 内容 1. 时间戳同步问题及意义 时间戳同步的原因&#xff1a;如果不同步&#xff0c;由于IMU频率高&#xff0c;可能由于时间戳不同步而导致在两帧camera之间的时间内用多了或者用少了IMU的数据&#xff0c;且时间不同步会导致我们首尾camera和IMU数据时间不同&#xff0c;…

65、内网安全-域环境工作组局域网探针方案

目录 案例1-基本信息收集操作演示案例2-网络信息收集操作演示案例3-用户信息收集操作演示案例4-凭据信息收集操作演示案例5-探针主机域控架构服务操作演示涉及资源 我们攻击内网一般是借助web攻击&#xff0c;直接进去&#xff0c;然后再去攻击内网&#xff0c;那么攻击的对象一…

Redis 命令 和 数据类型 您知道多少

文章目录 一、概述二、Redis 命令行客户端连接 Redis 服务器三、在 Redis 帮助命令的说明四、Redis 通用命令 generic4.1 通用命令说明4.1 keys 命令&#xff0c;列举出当前库的所有键4.2 type 命令&#xff0c;可以查看键对应值的类型4.3 object encoding 命令&#xff0c;查看…

深度学习 anaconda 安装问题

配置anaconda 在官网下载匹配版本的anaconda&#xff08;官网下载可能时间比较长&#xff09;&#xff0c;可以选择清华镜像。 安装过程默认即可&#xff0c;或者根据情况进行修改。 旧版本是可以在安装的时候勾选添加路径到环境变量中的&#xff0c;但是我安装的是2023.9月…

react 中setState 的三种写法

目录 1&#xff1a;使用对象形式的setState&#xff1a; 2&#xff1a;使用函数形式的setState: 3&#xff1a;使用回调函数&#xff1a; 1&#xff1a;使用对象形式的setState&#xff1a; this.setState({ count: 0 });2&#xff1a;使用函数形式的setState: this.setSt…

基于ResNet34的花朵分类

一.数据集准备 新建一个项目文件夹ResNet&#xff0c;并在里面建立data_set文件夹用来保存数据集&#xff0c;在data_set文件夹下创建新文件夹"flower_data"&#xff0c;点击链接下载花分类数据集https://storage.googleapis.com/download.tensorflow.org/example_i…

Qt QWebEngine 更换语言

背景 使用Qt QWebEngine开发的应用&#xff0c;在一些场景下&#xff0c;会显示英文文本&#xff0c;比如右键、JS弹出的对话框&#xff0c;所以需要进行汉化&#xff0c;更改语言。 准备翻译文件 Qt有提供翻译好的ts文件&#xff0c;我们可以直接下载ts文件qtwebengine_zh_…

深度学习——图像分类(CIFAR-10)

深度学习——图像分类&#xff08;CIFAR-10&#xff09; 文章目录 前言一、实现图像分类1.1. 获取并组织数据集1.2. 划分训练集、验证集1.3. 图像增广1.4. 引入数据集1.5. 定义模型1.6. 定义训练函数1.7. 训练模型并保存模型参数 二、生成一个桌面小程序2.1. 使用QT设计师设计界…

【Unity程序技巧】异步保险箱管理器

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

华为eNSP配置专题-路由策略的配置

文章目录 华为eNSP配置专题-路由策略的配置0、概要介绍1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、终端构成和连接2.2、终端的基本配置 3、配置路由策略3.1、目标3.2、配置路由策略 华为eNSP配置专题-路由策略的配置 0、概要介绍 路由策略就是通过一系列工具…

【云原生】portainer管理多个独立docker服务器

目录 一、portainer简介 二、安装Portainer 1.1 内网环境下&#xff1a; 1.1.1 方式1&#xff1a;命令行运行 1.1.2 方式2&#xff1a;通过compose-file来启动 2.1 配置本地主机&#xff08;node-1&#xff09; 3.1 配置其他主机&#xff08;被node-1管理的节点服务器&…

c++类和对象(1)

目录 目录&#xff1a; 1.类的定义 1.1:类的语法及相关概念 1.2:类的两种定义方式 1.3:类的成员变量名命名规范 2.类的访问限定符 2.1:三种访问限定符关键字引入 2.2:访问限定符的语法说明 2.3:c中struct与class的区别(面试常考) 3.类的作用域 4.类的实例化 5.类对象 5.1:类对…

uniapp实现webview页面关闭功能

实现思路&#xff1a; 1.关闭按钮是使用原生button添加的close属性。&#xff08;见page.json页面&#xff09; 2.监听关闭按钮的方法。&#xff08;onNavigationBarButtonTap&#xff09; 3.写实现关闭webview所有页面的逻辑。 废话不多说&#xff0c;直接上代码 1.page.…

《GB/T 8566-2022/ISO/IEC/IEEE:系统与软件工程生存周期过程》国家标准解读,附下载地址

关于企业架构、软件工程等相关内容&#xff0c;基本在行业内工作一段时间都能解释出各自的理解&#xff0c;网络资料更是知识爆炸&#xff0c;看似哪一种都对&#xff0c;其实相对都是个人理解&#xff0c;算不上严谨。 上周工作中涉及架构的企业标准编制审查&#xff0c;对严…

网工内推 | 国企,解决方案工程师,最高30k,有软考证书优先

01 中电信数智科技有限公司海南分公司 招聘岗位&#xff1a;解决方案经理&#xff08;ICT&#xff09; 职责描述&#xff1a; 1、负责调动前后端资源做好全省ICT业务的售前支撑服务工作。 2、根据实际项目需求&#xff0c;主动协同客户渠道开展ICT项目商机挖掘&#xff0c;促进…

用友U8SMSProxy -SQL注入漏洞

0x01 漏洞介绍 用友GRP-U8 R10政务管理软件是由用友政务公司基于云技术所推出的第十代政务产品。这款产品继承了用友R9、R9i、U8等行政事业版产品的各项优点&#xff0c;并融合了全国广大用户的最佳实践应用。它旨在为政府财政部门、社保部门、卫生部门、教育部门、民政部门、党…

【uniapp+云函数调用】人脸识别,实人认证,适用于app,具体思路解析,已实现

2023.10.8 需求: uniapp开发的app项目中使用人脸识别 app项目都是第一次搞,更别提人脸识别了。目前已有的就是Dcloud账号已申请,实现需求的时间没那么紧迫 此篇会详细记录从0到1的过程 2023.10.24 今天开始探究实现的过程 可能会记录的有些冗余 效果图如下: uniapp开发指南…

整个自动驾驶小车001:概述

材料&#xff1a; 1&#xff0c;树梅派4b&#xff0c;作为主控&#xff0c;这个东西有linux系统&#xff0c;方便 2&#xff0c;HC-S104超声波模块&#xff0c;我有多个&#xff0c;不少于4个&#xff0c;我可以前后左右四个方向都搞一个 3&#xff0c;l298n模块&#xff0c;…

【Linux】第三站:Linux基本指令(二)

文章目录 一、通配符 *二、man指令三、cp指令1.先给一个文件里面写入数据2. cp指令拷贝普通文件3.cp指令拷贝文件目录4.常用的选项总结 四、mv指令1.mv命令简介2.使用 五、一些插曲1.一些注意事项2.指令的本质3.再谈输出重定向4.追加重定向5.输入重定向 六、cat指令七、more指令…