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

news/2024/11/7 12:45:50/

👳我亲爱的各位大佬们好😘😘😘
♨️本篇文章记录的为 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 {@Overridepublic Boolean saveOrder(SaveOrder saveOrder) {System.out.println("save order, orderId : " + saveOrder.getId());return true;}@Overridepublic 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> {@Overridepublic OperateLogDO convert(SaveOrder saveOrder) {OperateLogDO operateLogDO = new OperateLogDO();operateLogDO.setId(saveOrder.getId());return operateLogDO;}
}

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

public class UpdateOrderConvert implements Convert<UpdateOrder> {@Overridepublic 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)@Overridepublic Boolean saveOrder(SaveOrder saveOrder) {System.out.println("save order, orderId : " + saveOrder.getId());return true;}@RecordOperate(desc = "更新订单", convert = UpdateOrderConvert.class)@Overridepublic 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);}@ResourceOrderService orderService;@Overridepublic 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.ppmy.cn/news/99963.html

相关文章

【Accessors注解】记录使用 lombook 注解姿势不对导致无法使用 BeanCopier 复制属性的问题

目录 背景定位问题分析原因为什么 BeanUtils.copyProperties() 可以为什么 BeanCopier 不可以 总结 背景 前几天看同事写的代码&#xff0c;发现不同分层对象之间的转换用的 spring 自带的 BeanUtils.copyProperties()&#xff0c;并且复制的还是对象集合。一时技痒&#xff0…

机器学习笔记 - 基于MATLAB的简单车牌识别系统参考代码

1、简述 车牌识别 (NPR) 是一种计算机视觉和模式识别技术,用于提取和解释车辆车牌上的字符。这里的重点是使用 MATLAB 实现一个简单的 NPR 系统,MATLAB 是一种用于科学计算和图像处理的强大编程语言和环境。目标是开发一个自动化系统,该系统可以检测图像中的车牌,从车牌中…

多元回归预测 | Matlab白鲸算法(BWO)优化BP神经网络回归预测,BWO-BP回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab白鲸算法(BWO)优化BP神经网络回归预测,BWO-BP回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %--------------…

表的增删改查

目录 表的增删改查create(创建)单行数据 全列插入多行数据 指定列插入插入否则更新替换 retrieve(读取)SELECT 列全列查询指定列查询查询字段为表达式为查询结果指定别名结果去重 WHERE 条件英语不及格的同学及英语成绩 ( < 60 )&#xff08;<&#xff09;语文成绩在 […

2.进程和线程

程序、进程、线程 概述 程序是静态的代码集合进程是程序在执行过程中的实例&#xff0c;是操作系统分配资源的基本单位线程是进程内的执行单位&#xff0c;用于实现并发执行和共享资源 程序&#xff08;Program&#xff09; 程序是指一组指令的集合&#xff0c;它是静态的、…

555定时器的基本原理和应用案例

前言 555定时器常用于脉冲波形的产生和整形电路中&#xff0c;之前在查找555定时器的原理图和基本管脚信息时&#xff0c;网上的内容大多含糊不清&#xff0c;没有讲的很详细&#xff0c;要么只是单一的管脚图&#xff0c;要么就是简单的文字解释&#xff0c;并且大多数缺乏基…

车载软件架构 —— 闲聊几句AUTOSAR OS(二)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 在最艰难的时候,自己就别去幻想太远的将来,只要鼓励自己过好今天就行了! 这世间有太多的猝不及防,有些东西根本不配占有自己的情绪,人生就是一场体验,…

LeetCode 128 最长连续序列

LeetCode 128 最长连续序列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/longest-consecutive-sequence/description/ 博主Github&#xff1a;https://github.com/GDUT-Rp/LeetCode 题目&#xff1a; 给定一个未排…