一、定义模型:订单保存模型,订单更新模型,业务层,日志模型
订单保存模型
/**
* @author durunwu
* @date 2024-08-20-21:04
*/
@Data
public class SaveOrder {
private Long id;
}
订单更新模型
/**
* @author durunwu
* @date 2024-08-20-21:05
*/
@Data
public class UpdateOrder {
private Long orderId;
}
Service业务层实现保存、更新等操作
/**
* @author durunwu
* @date 2024-08-20-21:03
*/
@Service
public class OrderService {
/**
* 单纯加注解是没用的,需要定义切面
*/
@RecordOperate(desc = "保存订单",convert = SaveOrderConvert.class)
public Boolean saveOrder(SaveOrder saveOrder){
System.out.println("save order, orderId: " + saveOrder.getId());
return true;
}
@RecordOperate(desc = "更新订单", convert = UpdateOrderConvert.class)
public Boolean updateOrder(UpdateOrder updateOrder){
System.out.println("update order, orderId: " + updateOrder.getOrderId());
return true;
}
}
定义日志模型用来记录订单保存、更新信息
/**
* @author durunwu
* @date 2024-08-20-21:14
*/
@Data
public class OperateLogDO {
private Long orderId;
private String desc;
private String result;
}
二、需求
在每次订单操作时,需要记录订单的日志信息,记录日志的操作都是一样的,我们可以提取出来作为公共方法提供使用
但是提取出来之后,怎样去记录不同订单类型的日志呢?
首先每个对象的类型不同、属性不同,难道需要通过判断入参的类型这样去实现吗?不够优雅
解决办法,使用aop切面
/**
* @author durunwu
* @date 2024-08-20-21:09
*/
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
OrderService orderService;
public static void main(String[] args) {
new SpringApplication(Application.class).run(args);
}
@Override
public void run(String... args) throws Exception {
/**
* 需求:
* 需要在每次订单操作时,记录订单的日志信息
* 记录日志这个操作都是一样的,需要提取出来单独处理
* 但是提出出来之后,怎样去记录不同订单类型的日志呢?难道需要通过判断入参的类型进行校验然后转型吗
* 解决办法,使用aop切面
*/
//保存订单,id=1
SaveOrder saveOrder = new SaveOrder();
saveOrder.setId(1L);
orderService.saveOrder(saveOrder);
//更新订单 id=2
UpdateOrder updateOrder = new UpdateOrder();
updateOrder.setOrderId(2L);
orderService.updateOrder(updateOrder);
}
}
三、Aop实现策略
具体代理实现流程
- 定义切入点
- 横切逻辑
- 植入
在业务层执行不同的业务操作时,通过环绕切面在方法执行之前和之后做操作,通过切入点ProceedingJoinPoint在方法执行之前通过反射获取到对应的方法签名MethodSignature,通过方法签名获取到当前注解@annotation,然后获取当前注解手动定义的类型Class,使用当前Class类型在Lambda泛化接口Convert< PARAM > 获取到对应日志模型实例,最后通过不同的业务模型去做具体的业务
注意:不推荐使用该操作,如果主业务发生了异常,切面逻辑是不能回滚的
1.Lambda泛化接口
抽象父接口
/**
* @author durunwu
* @date 2024-08-20-22:32
*/
public interface Convert<PARAM> {
/**
* 通过不同的入参,转换成标准的日志模型
*/
OperateLogDO convertDurunwu(PARAM param);
}
子实现1:实现订单保存的日志模型
/**
* @author durunwu
* @date 2024-08-20-22:34
*/
public class SaveOrderConvert implements Convert<SaveOrder>{
@Override
public OperateLogDO convertDurunwu(SaveOrder saveOrder) {
OperateLogDO operateLogDO = new OperateLogDO();
operateLogDO.setOrderId(saveOrder.getId());
return operateLogDO;
}
}
子实现2:实现订单更新的日志模型
/**
* @author durunwu
* @date 2024-08-20-22:35
*/
public class UpdateOrderConvert implements Convert<UpdateOrder>{
@Override
public OperateLogDO convertDurunwu(UpdateOrder updateOrder) {
OperateLogDO operateLogDO = new OperateLogDO();
operateLogDO.setOrderId(updateOrder.getOrderId());
return operateLogDO;
}
}
2.定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RecordOperate {
//定义每次操作的类型,默认不填
String desc() default "";
//定义一个类型,需要是Convert的子类
Class<? extends Convert> convert();
}
3.切面逻辑
package com.durunwu.study.demos.oAuth2.daily.aop;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 定义切面
* @author durunwu
* @date 2024-08-20-21:18
*/
@Aspect
@Component
public class OperateAspect {
/**
* 不推荐,主业务发生了异常,切面逻辑不能回滚
*
* 1.定义切入点,把注解RecordOperate的Class Path复制
* 2.横切逻辑
* 3.值入
*/
//定义切面方法
@Pointcut("@annotation(com.durunwu.study.demos.oAuth2.daily.aop.RecordOperate)")
public void pointcut() {};
//定义线程池
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)
);
//使用环绕通知,定义横向逻辑
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//切入点ProceedingJoinPoint
//拿到切入点的返回结果,这里如果抛异常则不记录流水,如果存在异常则抛出
Object result = proceedingJoinPoint.proceed();
//异步执行
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
//通过反射拿到方法签名
Signature signature = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//再通过方法签名拿到注解
Method method = methodSignature.getMethod();
//传入当前注解RecordOperate
RecordOperate annotation = method.getAnnotation(RecordOperate.class);
//使用注解的convert方法获取传入的类型
Class<? extends Convert> convert = annotation.convert();
//因为convert是一个类,需要实例化对象,获取对应的实现
Convert logConvert = convert.newInstance();
OperateLogDO operateLogDO = logConvert.convertDurunwu(proceedingJoinPoint.getArgs()[0]);
//构造写入流水的模型,
//OperateLogDO operateLogDO = new OperateLogDO();
//operateLogDO.setOrderId();
//获取注解上定义的操作类型
operateLogDO.setDesc(annotation.desc());
operateLogDO.setResult(result.toString());
//怎么才能获取对应类型的业务id呢? 难道需要if判断是什么类型的对象再去取对应的id?这样不太优雅
//解决思路:定义Lambda接口,通过不同的类型去转换,通过不同的入参去拿到一个不同的标准模型
//定义Convert接口,入参泛化Convert<PARAM>
System.out.println("isnert operateLog" + operateLogDO);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
});
return result;
}
}
四、Debug测试
控制台输出同步操作和异步操作