👳我亲爱的各位大佬们好😘😘😘
♨️本篇文章记录的为 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切面方式实现流水日志输出,并且代码无侵入,满足开闭原则!
文章来源:https://www.toymoban.com/news/detail-462670.html
如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对Java后端或者对
spring
,分布式
,云原生
感兴趣的朋友,请多多关注💖💖💖
👨🔧 个人主页 : 阿千弟文章来源地址https://www.toymoban.com/news/detail-462670.html
到了这里,关于开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!