开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞

news2025/1/11 2:43:20

👳我亲爱的各位大佬们好😘😘😘
♨️本篇文章记录的为 JDK8 新特性 Stream API 进阶 相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬🙉🙉🙉。
♨️如果文章有什么需要改进的地方还请大佬不吝赐教❤️🧡💛
👨‍🔧 个人主页 : 阿千弟
🔥 相关内容👉👉👉 : 都2023年了,如果不会Lambda表达式、函数式编程?你确定能看懂公司代码?

最近工作中遇到了一个新的需求, 让我把前端发送请求的所有操作统统用日志记录下来, 但是呢前端是h5的页面, 任何一个对页面的操作都记录下来也不太现实, 于是AOP这时候就是一个很好的选择, 但是在操作很多的状态下, 对每一个操作都写一个切面来记录也不太现实, 应优先考虑如何对切面进行抽取与拓展.

因为代码逻辑比较复杂, 所以每次排查问题的时候都非常耗时, 所以这时候用AOP的方式需合的就是在每一个操作的方法之前去加一个注解,通过切面处理这个方法义步的去协助流水表
在这里插入图片描述

当前场景

为了把保存订单和更新订单的方便区分, 我们分别用不同的实体类entity来表示

SaveOrder

@Data
public class SaveOrder {
    private Long id;
}

UpdateOrder

@Data
public class UpdateOrder {
    private Long OrderId;
}

下面是Service层接口

public interface OrderService {

    Boolean saveOrder(SaveOrder saveOrder);

    Boolean updateOrder(UpdateOrder updateOrder);
}

下面是impl实现类

@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Boolean saveOrder(SaveOrder saveOrder) {
        System.out.println("save order, orderId : " + saveOrder.getId());
        return true;
    }

    @Override
    public Boolean updateOrder(UpdateOrder updateOrder) {
        System.out.println("update order, orderId : " + updateOrder.getOrderId());
        return true;
    }
}

上面的代码很简单,很方便的就能看懂, 下面我们就来编写自定义注解和AOP切面

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordOperate {

	//desc 是用来放日志类型的描述
    String desc() default " ";
	//convert 用来放日志类型的转变类
    Class<? extends Convert> convert();

}

我们可以看到在自定义的注解类中有一个desc()属性, 它用于记录的字段信息;

还有一个convert()方法, 这个方法很关键 :

当我们每次调用 OrderService 的时候, 要根据不同的方法进行不同日志记录, 那么我们怎么知道, 我们调用的是 SaveOrder 还是 UpdateOrder , 所以不管是SaveOrder还是UpdateOrder, 我们都需要记录日志, 但是呢, 我们不可能对每个操作都单独的写一个切面记录日志, 所以呢需要写一个 convert() 对不同的方法属性转化成统一的log实体输出

下面编写转换器convert()

代码很简单, 泛型用PARAM表示, 返回结果是约定好的OperateLogDO

public interface Convert<PARAM> {
    OperateLogDO convert(PARAM param);
}

下面的代码是转换器的用法

SaveOrderConvert通过实现接口来重写convert()方法

public class SaveOrderConvert implements Convert<SaveOrder> {
    @Override
    public OperateLogDO convert(SaveOrder saveOrder) {
        OperateLogDO operateLogDO = new OperateLogDO();
        operateLogDO.setId(saveOrder.getId());
        return operateLogDO;
    }
}

UpdateOrderConvert通过实现接口来重写convert()方法

public class UpdateOrderConvert implements Convert<UpdateOrder> {
    @Override
    public OperateLogDO convert(UpdateOrder updateOrder) {
        OperateLogDO operateLogDO = new OperateLogDO();
        operateLogDO.setId(updateOrder.getOrderId());
        return operateLogDO;
    }
}

OperateLogDO实体

@Data
public class OperateLogDO {
    private Long id;
    public String desc;
    public String result;
}

AOP切面

@Component
@Aspect
public class OperateAspect {
    /**
     * 定义切入点
     * 横切逻辑
     * 织入
     */

    @Pointcut("@annotation(com.example.demo_aop.annotation.RecordOperate)")
    public void pointCut(){};
	
	//定义线程池的目的是记录日志不需要特别强的同步性, 所以创建线程异步记录
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            1,1,1,TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)
    );

    @Around("pointCut()")
    public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = proceedingJoinPoint.proceed();
        threadPoolExecutor.execute(() -> {
        //这段Java代码获取了一个切点方法的签名信息,
        //并且通过反射获取了该方法上的RecordOperate注解。
            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            RecordOperate annotation = methodSignature.getMethod().getAnnotation(RecordOperate.class);

			//通过反射判断是哪个方法的转换器,并获取
            Class<? extends Convert> convert = annotation.convert();
            OperateLogDO operateLogDO = null;
            try {
            //创建Convert类的一个新实例
                Convert logConvert = convert.newInstance();
                operateLogDO = logConvert.convert(proceedingJoinPoint.getArgs()[0]);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            operateLogDO.setDesc(annotation.desc());
            operateLogDO.setResult(result.toString());
            System.out.println("insert operateLog " + JSON.toJSONString(operateLogDO));
        });
        return result;
    }
}

这段切面代码也比较的简单, 但是呢需要具备一定的java基础功底

自定义注解应用

@Service
public class OrderServiceImpl implements OrderService {

    @RecordOperate(desc = "保存订单", convert = SaveOrderConvert.class)
    @Override
    public Boolean saveOrder(SaveOrder saveOrder) {
        System.out.println("save order, orderId : " + saveOrder.getId());
        return true;
    }

    @RecordOperate(desc = "更新订单", convert = UpdateOrderConvert.class)
    @Override
    public Boolean updateOrder(UpdateOrder updateOrder) {
        System.out.println("update order, orderId : " + updateOrder.getOrderId());
        return true;
    }
}

代码测试

@SpringBootApplication
public class AopApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
    }
   
    @Resource
    OrderService orderService;


    @Override
    public void run(String... args) throws Exception {
        SaveOrder saveOrder = new SaveOrder();
        saveOrder.setId(1L);
        orderService.saveOrder(saveOrder);

        UpdateOrder updateOrder = new UpdateOrder();
        updateOrder.setOrderId(2L);
        orderService.updateOrder(updateOrder);
    }
}
(JVM running for 5.141)
save order, orderId : 1
update order, orderId : 2
insert operateLog {"desc":"保存订单","id":1,"result":"true"}
insert operateLog {"desc":"更新订单","id":2,"result":"true"}

大家也可以看到,这样做的好处是通过抽取公共组件,使用aop切面方式实现流水日志输出,并且代码无侵入,满足开闭原则!

在这里插入图片描述

如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对Java后端或者对spring, 分布式, 云原生感兴趣的朋友,请多多关注💖💖💖
👨‍🔧 个人主页 : 阿千弟

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

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

相关文章

使用腾讯云服务器快速搭建网站教程

已经有了腾讯云服务器如何搭建网站&#xff1f;腾讯云服务器网以腾讯云服务器&#xff0c;借助宝塔面板搭建Web环境&#xff0c;然后使用WordPress博客程序搭建网站&#xff0c;大致分为三步&#xff0c;首先购买腾讯云服务器&#xff0c;然后在腾讯云服务器上部署宝塔面板&…

Vue + Vite 构建 自己的ChartGPT 项目

前期回顾 两分钟学会 制作自己的浏览器 —— 并将 ChatGPT 接入_彩色之外的博客-CSDN博客自定义浏览器&#xff0c;并集合ChatGPT&#xff0c;源码已公开https://blog.csdn.net/m0_57904695/article/details/130467253?spm1001.2014.3001.5501 目录 效果图 代码步骤&am…

【Linux】软件包管理器/编辑器/yum是应用商店?/vim编辑器什么?

本文思维导图&#xff1a; 文章目录 Linux软件安装关于Linux的软件生态 1.Linux软件包管理器&#xff1a;yum到底是什么关于yum指令&#xff1a;关于yum源 2. rzsz指令1. Linux编辑器——vim编辑器vim编辑器的三种主要模式vim编辑器命令模式常用快捷键&#xff1a;vim操作总结…

spring(事务管理)

事物可以看做是由对数据库若干操作组成的一个单元 事务的作用就是为了保证用户的每一个操作都是可靠的&#xff0c;事务中的每一步操作都 必须成功执行&#xff0c;只要有发生异常就回退到事务开始未进行操作的状态,这些操作 要么都完成&#xff0c;要么都取消&#xff0c;从而…

Linux:/dev/tty、/dev/tty0 和 /dev/console 之间的区别

在Linux操作系统中&#xff0c;/dev/tty、/dev/tty0和/dev/console是三个特殊的设备文件&#xff0c;它们在终端控制和输入/输出过程中扮演着重要的角色。尽管它们看起来很相似&#xff0c;但实际上它们之间存在一些重要的区别。本文将详细介绍这三个设备文件之间的区别以及它们…

浅谈如何fltk项目编译和实现显示中文

目录 一、编译 二、中文显示如何处理&#xff1a; 2.1在发文2天前突然发现&#xff0c;我这个界面显示英文出现问题了&#xff0c;开始我的搜索之旅&#xff0c;一些参考页面有碰到问题也可以看看&#xff1a; 2.2、 那就开始翻翻官方自带的例程吧&#xff0c;看看他如何显…

Join的连接原理

1. 连接简介 1.1 连接的本质 连接就是把各个表中的记录都取出来进行一次匹配&#xff0c;并把匹配后的组合发送给客户端。如果连接查询中的结果集中包含一个表中的每一条记录与另一个表中的每一条记录相互匹配的组合&#xff0c;那么这样的结果集就可以称为笛卡尔积。 1.2 连…

计算机网络基础知识(七)—— 什么是HTTPS协议?你听我“瞎掰”

文章目录 01 | 工作原理02 | SSL/TLS协议2.1 | 握手协议2.2 | 更换密码协议&#xff08;Change Cipher Spec Protocol&#xff09;2.3 | 警告协议&#xff08;Alert Protocol&#xff09;2.4 | 应用数据协议&#xff08;Application Data Protocol&#xff09; 03 | 加密算法3.…

CSRF及SSRF漏洞案例讲解(29)

讲解一下这个图片&#xff0c;用户在浏览器登陆银行界面发送一个请求&#xff0c;通过转账&#xff0c;转载的数据包假如是下面那串字符&#xff0c;黑客呢就自己一个网站或控制一个网站&#xff0c;去写入一个代码&#xff0c;这个代码就是请求这个数据包&#xff0c;刚好这个…

人工智能学习07--pytorch19--目标检测:常见指标(mAP计算+coco评价标准)

怎样才算正确检测到一个目标&#xff1f; 什么是IOU&#xff1a; https://blog.csdn.net/qq_51831335/article/details/125719420 mAP计算方法&#xff1a; 假设针对某一类别的AP情况 TP&#xff1a;预测正确的边界框个数。预测边界框与GT-box的IOU>0.5 FP&#xff1a;假…

原工程运行正常,重新复制一份后再 npm install 后再运行就报错的解决办法

原工程&#xff0c;运行正常 将刚刚的工程复制一份呢&#xff0c;重新 npm install 再 npm run serve 就报错 出现这个问题十之八九都是依赖的问题。有可能是因为这个工程里面之前安装过一些东西&#xff0c;后来莫名其妙的就把 package.json 里面相关的依赖给删掉了。但由于原…

lwIP 开发指南

目录 lwIP 初探TCP/IP 协议栈是什么TCP/IP 协议栈架构TCP/IP 协议栈的封包和拆包 lwIP 简介lwIP 源码下载lwIP 文件说明 MAC 内核简介PHY 芯片介绍YT8512C 简介LAN8720A 简介 以太网接入MCU 方案 lwIP 无操作系统移植lwIP 带操作系统移植ARP 协议ARP 协议的简介ARP 协议的工作流…

uni-app项目运行和项目结构目录讲解

UNI-APP学习系列 uni-app项目运行和项目结构目录讲解 文章目录 UNI-APP学习系列前言总结 前言 UNI-APP学习系列之uni-app项目运行和项目结构目录讲解 运行项目 使用 pnpm 包管理工具 # 查看是否安装pnpmpnpm -v# 无则安装npm install -g pnpm下载依赖 pnpm i运行pnpm dev:h…

Window的创建

Window的创建 上一篇说到了Window和WindowManager的关系并且讲述了WindowManager如何添加Window与Window内部的三个方法的实现 这篇主要讲几个常见的Window的创建比如Activity,Dialog和Toast 其中Activity属于应用Window Dialog属于子Window Toast属于系统Window z-order…

python基础知识(二):变量和常用数据类型

目录 1. 变量1.1 变量的定义1.2 变量的命名规则 2. 常用数据类型2.1 字符串2.1.1 字符串的常用方法2.1.1.1 title()方法&#xff1a;将字符串中的单词首字母大写2.1.1.2 upper()方法&#xff1a;将字符串中的单词字母全大写2.1.1.3 lower()方法&#xff1a;将字符串中的单词字母…

什么是分段路由?如何在网络中实施分段路由?

在计算机网络中&#xff0c;分段路由&#xff08;Subnetting&#xff09;是一种将一个大的网络划分为多个较小子网的技术。它允许网络管理员更有效地分配 IP 地址和管理网络流量。本文将详细介绍分段路由的概念、原理以及如何在网络中实施分段路由。 1. 分段路由的概念 分段路…

【深入浅出Spring Security(一)】Spring Security的整体架构

Spring Security的整体架构 一、整体架构认证&#xff08;Authentication&#xff09;AuthenticationManagerAuthentication登录后的数据保存&#xff08;SecurityContextHolder&#xff09; 授权&#xff08;Authorization&#xff09;ConfigAttribute 二、总结 这篇博客所述主…

CISCN 2023 初赛 pwn——Shellwego 题解

这是一个用go语言写的elf程序&#xff0c;没有PIE。这也是本蒟蒻第一次解go pwn题&#xff0c;故在此记录以便参考。 而且&#xff0c;这还是一个全部符号表被抠的go elf&#xff0c;直接面对一堆不知名的函数实在有些应付不来&#xff0c;因此在比赛时委托逆向的队友把符号表…

2023/5/28总结

static static:静态&#xff0c;可以修饰成员方法&#xff0c;成员变量。&#xff08;是所有成员共享的&#xff09; static修饰的特点&#xff1a; 被类的所有对象共享&#xff08;判断是否使用静态关键字的条件&#xff09;可以通过类名和对象名调用在定义对象时&#xff0c;…

图【数据结构】

目录 一、图的定义和基本术语 二、图的类型定义 三、图的存储结构 1、数组&#xff08;邻接矩阵&#xff09;表示法 二、邻接表&#xff08;链式&#xff09;表示法 三、图的邻接表的存储表示 四、十字链表与邻接多重链表 &#xff08;1&#xff09;十字链表 &#xff…