文章目录
- 前言
- 一、切面编程(AOP)是什么?
- 二、demo样例
- 1.实体类
- a.新增订单
- b.更新订单
- b.日志实体类
- 2.实现相关
- a.类型转换接口类
- b.类型转换接口实现类
- c.自定义注解
- d.切面配置
- e.运行
- 三、结果示例
- 四、其他
前言
项目中有很多场景需要去记日志,,也可能存在有很多接入方,日志表是同一种,但是外部数据的字段可能与日志表不一一对应,如果每一种业务都写一种方法去做转化,代码量很大,因此可采用横向切入逻辑去做一个整体处理,异步去写入,对代码的侵入也相关较少。
一、切面编程(AOP)是什么?
- 切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以提高代码的模块化性、可维护性和复用性。
- AOP 主要通过切面(Aspect)和连接点(Join Point)来实现,其中:切面(Aspect)是横切关注点的模块化单元,它包含了通知(Advice)和切点(Pointcut)。连接点(Join Point)是在应用执行过程中能够插入切面的点,通常是方法的调用或异常的抛出等。
二、demo样例
1.实体类
a.新增订单
@Data
public class SaveOrder {
//订单ID
private String id;
//订单名称
private String name;
}
b.更新订单
@Data
public class UpdateOrder {
//订单ID
private String orderId;
//订单名称
private String name;
}
b.日志实体类
@Data
public class OperateLogDO {
//订单ID
private String orderId;
//描述
private String desc ;
//结果
private String result ;
}
2.实现相关
a.类型转换接口类
//参数类型定义为泛型
public interface Convert<PARAM> {
OperateLogDO convert (PARAM param);
}
b.类型转换接口实现类
//新增订单对象转换
public class SaveOrderConvert implements Convert<SaveOrder>{
@Override
public OperateLogDO convert(SaveOrder saveOrder) {
OperateLogDO operateLogDO = new OperateLogDO();
operateLogDO.setOrderId(saveOrder.getId());
return operateLogDO;
}
}
//更新订单对象转换
public class UpdateOrderConvert implements Convert<UpdateOrder>{
@Override
public OperateLogDO convert(UpdateOrder updateOrder) {
OperateLogDO operateLogDO = new OperateLogDO();
operateLogDO.setOrderId(updateOrder.getOrderId());
return operateLogDO;
}
}
c.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordOperate {
String desc() default "";
Class<? extends Convert> convert();
}
d.切面配置
@Component
@Aspect
public class OperateAop {
//定义线程池
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
1,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));
//匹配被 @RecordOperate 注解标记的方法作为切入点
@Pointcut("@annotation(com.example.demo.aop.RecordOperate)")
public void pointcut(){
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
Object rs = proceedingJoinPoint.proceed();
threadPoolExecutor.execute(()->{
try {
//获取当前连接点的方法签名信息,并将其强制转换为 MethodSignature 类型
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//通过 signature.getMethod() 获取方法对象,然后调用 getAnnotation() 方法
//传入 RecordOperate.class 参数,从而获取该方法上标记的 RecordOperate 注解
//对象
RecordOperate annotation = signature.getMethod().getAnnotation(RecordOperate.class);
//通过获取到的 RecordOperate 注解对象,调用 convert() 方法获取注解中
//指定的转换类的 Class 对象
Class<? extends Convert> convert = annotation.convert();
//实例化对象
Convert logConvert = convert.newInstance();
//获取参数,因上述方法只有一个参数,故取下标为0的值
Object arg = proceedingJoinPoint.getArgs()[0];
//调用转换方法
OperateLogDO operateLogDO = logConvert.convert(arg);
//设置结果
operateLogDO.setDesc(annotation.desc());
operateLogDO.setResult(rs.toString());
System.out.println("operateLogDO = " + operateLogDO);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
return rs;
}
}
e.运行
//在main类中实现CommandLineRunner接口进行测试
@Override
public void run(String... args) {
SaveOrder saveOrder = new SaveOrder();
saveOrder.setId("1");
saveOrder.setName("save");
orderService.saveOrder(saveOrder);
UpdateOrder updateOrder = new UpdateOrder();
updateOrder.setOrderId("2");
updateOrder.setName("update");
orderService.updateOrder(updateOrder);
}
三、结果示例
实现了不同的字段转换为同一字段。
四、其他
- 主业务发生了异常,切面逻辑不能回滚,导致记录异常;
- 实际项目中应该采用模版类方法,把公共属性抽到一个父类,更方便写统一的aop。
- AOP 的核心概念包括以下几种通知类型:
前置通知(Before advice):在连接点之前执行,常用于执行一些预处理操作。
后置通知(After returning advice):在连接点正常执行后执行,常用于执行一些清理操作。
环绕通知(Around advice):包围连接点执行,可以在方法调用前后进行一些额外的处理。
异常通知(After throwing advice):在连接点抛出异常后执行,用于处理异常情况。
最终通知(After finally advice):无论连接点是否正常执行完成,最终都会执行的通知。