👳我亲爱的各位大佬们好😘😘😘
♨️本篇文章记录的为 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
,分布式
,云原生
感兴趣的朋友,请多多关注💖💖💖
👨🔧 个人主页 : 阿千弟