使用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("pt()")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.rhkb.cn/news/170937.html

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

相关文章

将CSDN或Confluence文章转为微信公众号格式

最近在更公众号文章&#xff0c;苦于排版和格式&#xff0c;就找了一个比较方便的方法&#xff0c;简单易用&#xff0c;排版也不错。 文章提取 有的文章是已经发布在其它平台了&#xff0c;比如CSDN或Confluence&#xff0c;可以使用飞书剪存方便的将文章提取出来&#xff0…

垃圾回收系统小程序

在当今社会&#xff0c;废品回收不仅有利于环境保护&#xff0c;也有利于资源的再利用。随着互联网技术的发展&#xff0c;个人废品回收也可以通过小程序来实现。本文将介绍如何使用乔拓云网制作个人废品回收小程序。 1. 找一个合适的第三方制作平台/工具&#xff0c;比如乔拓云…

人工智能基础_机器学习001_线性回归_多元线性回归_最优解_基本概念_有监督机器学习_jupyter notebook---人工智能工作笔记0040

线性和回归,就是自然规律,比如人类是身高趋于某个值的概率最大,回归就是通过数学方法找到事物的规律. 机器学习作用: 该专业实际应用于机器视觉、指纹识别、人脸识别、视网膜识别、虹膜识别、掌纹识别、专家系统、自动规划、智能搜索、定理证明、博弈、自动程序设计、智能控制…

公众号迁移如何线上办理公证?

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;1.可合并多个公众号的粉丝、文章&#xff0c;打造超级大V2.可变更公众号主体&#xff0c;更改公众号名称&#xff0c;变更公众号类型——订阅号、服务号随意切换3.可以增加留言功能4.个人订阅号可迁移到企业名…

Go学习第十一章——协程goroutine与管道channel

Go协程goroutine与管道channel 1 协程goroutine1.1 基本介绍1.2 快速入门1.3 调度模型&#xff1a;MPG模式介绍1.4 设置cpu数1.5 协程资源竞争问题1.6 解决协程并发方案 2 管道channel2.1 基本介绍2.2 快速入门2.3 管道的关闭和遍历2.4 管道和协程的结合2.5 声明 只读/只写 的管…

最新SQL注入漏洞修复建议

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 SQL注入漏洞修复建议 常用的SQL注入漏洞的修复方法有两种。 1&#xff0e;过滤危险字符 多数CMS都采用过滤危险字符的方式&#xff0c;例如&…

小程序配置请求代理

在app.json中添加“proxy”字段配置代理 "proxy": {"/api": {"target": "http://192.168.110.249:8221/",//你要请求的目标地址"changeOrigin": true,"pathRewrite": {"^/api": ""//重定向}}…

【GIT】:一文快速了解什么是GIT

【GIT】&#xff1a;一文快速了解什么是GIT 个人主页: 【⭐️个人主页】 需要您的【&#x1f496; 点赞关注】支持 &#x1f4af; 关于版本控制 什么是“版本控制”&#xff1f;我为什么要关心它呢&#xff1f; 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来…

C++设计模式_14_Facade门面模式

本篇介绍的Facade门面模式属于“接口隔离”模式的一种&#xff0c;以下进行详细介绍。 文章目录 1. “接口隔离”模式1. 1 典型模式 2. 系统间耦合的复杂度3. 动机(Motivation)4. 模式定义5. Facade门面模式的代码实现6. 结构7. 要点总结8. 其他参考 1. “接口隔离”模式 在组…

如何为你的地图数据设置地图样式?

地图样式设置是GIS系统中非常重要的功能模块&#xff0c;水经微图Web版本最近对符号样式功能模块进行了升级。 你可以通过以下网址直接打开访问&#xff1a; https://map.wemapgis.com 现在我们为大家分享一下水经微图Web版中&#xff0c;如何为你标注的地图数据设置地图样式…

【C++进阶】pair容器

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

code too large

描述&#xff1a;比较尴尬&#xff0c;一个方法的代码接近10000行了&#xff0c;部署服务器的时候提示(java :code[255,21] too large),提示代码过长&#xff0c;无法运行。 查看了一下百度&#xff1a;解决的思路 JVM规范&#xff1a;「类或接口可以声明的字段数量限制在 655…

数据结构与算法基础(青岛大学-王卓)(9)

终于迎来了最后一部分(排序)了&#xff0c;整个王卓老师的数据结构就算是一刷完成了&#xff0c;但是也才是数据结构的开始而已&#xff0c;以后继续与诸位共勉 &#x1f603; (PS.记得继续守护家人们的健康当然还有你自己的)。用三根美味的烤香肠开始吧。。。 文章目录 排序基…

maya2023安装

1、解压压缩包&#xff0c;点击setup安装&#xff0c;除修改安装路径外&#xff0c;其他都是都是下一步&#xff0c;安装后最好重启系统 破解步骤 关闭杀毒&#xff0c;防止误删1.安装Autodesk软件&#xff0c;但是不要启动&#xff0c;安装完成后重启电脑 2.安装破解文件夹里…

Python轮廓追踪【OpenCV形态学操作】

文章目录 概要代码运行结果 概要 一些理论知识 OpenCV形态学操作理论1 OpenCV形态学操作理论2 OpenCV轮廓操作|轮廓类似详解 代码 代码如下&#xff0c;可以直接运行 import cv2 as cv# 定义结构元素 kernel cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) # print kern…

星途星纪元 ES,用艺术思维表达工程技术

10月8日&#xff0c;星途星纪元ES携手世界级成都爱乐首席乐团、旅德青年钢琴家王超&#xff0c;在成都打造了一场“万物星声”超舒适音乐会视听盛宴。这是星途星纪元首次跨界音乐圈、牵手音乐挚友&#xff0c;共同演绎音乐和汽车的美学协奏曲&#xff0c;开启高端超舒适美学新纪…

汉威科技光纤预警系统,守护油气长输管道“大动脉”

石油、天然气早已成为城市生活中不可或缺的能源。广大车主能快速地加上汽油&#xff0c;千家万户能方便地用上天然气&#xff0c;得益于我国庞大的石油、天然气输送基础设施网络。 我国油气分布西多东少、北多南少&#xff0c;要想把千里、乃至万里之外的石油、天然气输送到中部…

竞赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

JavaScript进阶知识汇总~

JavaScript 进阶 给大家推荐一个实用面试题库 1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;web前端面试题库 1.原型链入门 1) 构造函数 当我们自定义一个函数时(箭头函数与生成器函数除外)&#xff0c;这个函…

kafka入门03——简单实战

目录 安装Java 安装Zookeeper 安装Kafka 生产与消费 主要是记录下Kafka的安装配置过程&#xff0c;前置条件需要安装jdk和zookeeper。 安装Java 1.Oracle官网下载对应jdk安装包 官网地址&#xff1a;Java Downloads | Oracle 好人分享了下载需要的oracle账号&#xff0c…