使用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();
        // ip
        String ip = IPUtils.getIpAddr(httpServletRequest);
        // 请求url
        String 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 {

    @Autowired
    HttpServletRequest 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();
        // 执行切点方法M
        Object 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();
        // ip
        String ip = IPUtils.getIpAddr(httpServletRequest);
        // 请求url
        String 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等代理的ip
            if (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集群获取ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                    //根据网卡取本机配置的IP
                    InetAddress 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;
    }
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

seacms_CNVD-2020-22721_v10.1漏洞分析与复现

seacms 远程命令执行漏洞复现 文章目录 seacms 远程命令执行漏洞复现一、基本信息二、组件简介三、漏洞详情漏洞介绍影响范围危害 四、防御1. 漏洞存在性检测2. 修复建议3. 规避措施4. 漏洞利用检测 五、漏洞复现1. 复现环境2. 漏洞复现 一、基本信息 titlecontentnote漏洞编号…

【Redis】Docker部署Redis数据库

Docker部署Redis数据库 1. Redis介绍2. CentOS 7 安装 & Docker 配置3. 拉取Redis 镜像、创建容器3.1 配置Docker镜像源3.2 拉取Redis 镜像3.3 容器创建 1. Redis介绍 Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用…

二、【常用的几种抠图方式一】

文章目录 选框抠图快速选择工具抠图魔棒工具抠图对象选择工具抠图套索工具抠图多边形套索工具抠图磁性套索工具抠图 选框抠图 选框工具抠图适合规则的图形&#xff0c;如下图先使用选框工具框出对象的图轮廓&#xff0c;然后再选择并遮住在里边擦出图形的边缘&#xff0c;根据…

【LeetCode:1465. 切割后面积最大的蛋糕 | 贪心 + 排序】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

安全和便捷:如何将运营商二要素API应用于实名制管理中

引言 随着互联网的快速发展&#xff0c;数字化身份验证和实名制管理变得越来越重要。在金融、电子商务、社交媒体等领域&#xff0c;确保用户身份的安全和准确性至关重要。运营商二要素核验API成为了实名制管理的有力工具&#xff0c;它不仅能够提供高水平的安全性&#xff0c…

常见的云测试策略及重要性

随着云计算技术的快速发展&#xff0c;云服务已经成为了现代应用程序开发和部署的核心组成部分。然而&#xff0c;随之而来的是对云系统性能和质量的不断追求&#xff0c;这使得云测试变得至关重要。本文将探讨云测试的概念、重要性以及一些常见的云测试策略和工具。 一、云测试…

jvm摘要

第 2 章 Java 内存区域与内存溢出异常 2.2 运行时数据区域 程序计数器-线程私有:是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。 程序计数器是唯一一个没有规定任何OutOfMemoryError 情况的区域。 Java 虚拟机栈-线程私有:用于执行Java …

PlantSimulation访问本地Excel文件的方法

PlantSimulation访问本地Excel文件的方法 PlantSimulation访问本地Excel文件的方法PlantSimulation访问本地Excel文件的方法 //Param StatusTable,T_DataTable:object var T_DataTable:object:=DataTable IF NOT isComputerAccessPermittedMESSageBox("计算机访问被阻止,…

如何在群晖NAS中搭建WebDav服务,并实现公网访问

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学数据结构》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 1. 在群晖套件中心安装WebDav Server套件1.1 安装完成后&#xff0c;启动webdav服务&#xff0c;并勾选HTTP复选…

uniapp 单选框以及多选框样式更改

radio以及checkbox默认样式不符合自身需求时&#xff0c;根据自身需求更改样式&#xff0c;以下是自身的示例&#xff1a; 单选&#xff1a; 多选&#xff1a; 由于uniapp自身包含了一套默认的样式&#xff0c;所以如果不想全局更改只想在某个单据页面使用的话&#xff0c;就…

22年上半年下午题

第一大题题目 第一大题解答 第一小问 看加工交互和说明来得出实体的名字。如果不太确定&#xff0c;可以多去看几条数据流来确认答案。仔细一点&#xff0c;这分稳啦。 第二小问 需要对应加工结合说明得出数据存储的名称。 一般可以在后面加上表字或者加上信息表。自拟&…

腾讯云轻量应用服务器简介_轻量服务器购买指南

腾讯云轻量应用服务器性能如何&#xff1f;为什么便宜是不是性能不行&#xff1f;腾讯云百科txybk.com从轻量应用服务器的CPU型号、处理器主频、内存、公网带宽、月流量和系统盘多方面来详细测评轻量性能&#xff0c;轻量应用服务器性价比高&#xff0c;并不是性能不行&#xf…

Docker 搭建 LNMP + Wordpress

[TOC](Docker 搭建 LNMP Wordpress 一、项目介绍1.1、项目环境1.2、 服务器环境1.3、 任务需求 二、部署Nginx2.1、建立工作目录2.2、 编写 Dockerfile 脚本2.3、准备 nginx.conf 配置文件2.4、生成镜像2.5、创建自定义网络 三、部署Mysql3.1、建立工作目录3.2、编写 Dockerfi…

springboot+vue基于协同过滤算法的私人诊所管理系统的设计与实现【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

18亿欧元大动作,法国瞄准实现量子飞跃

Quobly 正在开发一种容错量子处理器&#xff08;图片来源&#xff1a;网络&#xff09; 2021年1月&#xff0c;马克龙总统宣布了法国国家量子计算计划&#xff0c;并将为该技术投入高达18亿欧元。 “量子战略至关重要&#xff0c;”马克龙在量子研究中心巴黎萨克雷大学宣布该…

SpringBoot集成Redis Cluster集群(附带Linux部署Redis Cluster高可用集群)

目录 一、前言二、集成配置2.1、POM2.2、添加配置文件application.yml2.3、编写配置文件2.4、编写启动类2.5、编写测试类测试是否连接成功 一、前言 这里会使用到spring-boot-starter-data-redis包&#xff0c;spring boot 2的spring-boot-starter-data-redis中&#xff0c;默…

小红书内容运营包含哪些,内容种草攻略

在这个社交属性&#xff0c;强势泛滥的年代&#xff0c;兼具了社交和电商两大功能的小红书&#xff0c;已经成为品牌方的兵家必争之地。今天来为大家分享下小红书内容运营包含哪些&#xff0c;内容种草攻略&#xff01; 1、确定账号定位 这是做好小红书内容运营的第一步。一个有…

2018年亚太杯APMCM数学建模大赛A题老年人平衡能力的实时训练模型求解全过程文档及程序

2018年亚太杯APMCM数学建模大赛 A题 老年人平衡能力的实时训练模型 原题再现 跌倒在老年人中很常见。跌倒可能会导致老年人出现许多并发症&#xff0c;因为他们的康复能力通常较差&#xff0c;因此副作用可能会使人衰弱&#xff0c;从而加速身体衰竭。此外&#xff0c;对跌倒…

MySQL数据库#6

Python操作mysql 在使用Python连接mysql之前我们需要先下载一个第三方的模块 pymysql的模块&#xff0c;导入后再进行操作。 操作步骤&#xff1a;1. 先连接mysql host&#xff0c;port&#xff0c;charset&#xff0c;username password 库&#xff0c;等等。 import pymysql…

第16章总结

.1.1&#xff1a;访问构造方法 反射&#xff1a; 1.class类 2.获取构造方法 3.获取成员属性 4.获取成员方法 注解 1.内置注解 2.反射注解 3 创建Class对象的三种方式 1.使用getClass&#xff08;&#xff09;方法 object str new object&#xff08;&#xff09;…