学习视频
一、定义模型:订单保存模型,订单更新模型,业务层,日志模型
订单保存模型
/*** @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 {@AutowiredOrderService orderService;public static void main(String[] args) {new SpringApplication(Application.class).run(args);}@Overridepublic void run(String... args) throws Exception {/*** 需求:* 需要在每次订单操作时,记录订单的日志信息* 记录日志这个操作都是一样的,需要提取出来单独处理* 但是提出出来之后,怎样去记录不同订单类型的日志呢?难道需要通过判断入参的类型进行校验然后转型吗* 解决办法,使用aop切面*///保存订单,id=1SaveOrder saveOrder = new SaveOrder();saveOrder.setId(1L);orderService.saveOrder(saveOrder);//更新订单 id=2UpdateOrder 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>{@Overridepublic 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>{@Overridepublic 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() {@Overridepublic void run() {try {//通过反射拿到方法签名Signature signature = proceedingJoinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;//再通过方法签名拿到注解Method method = methodSignature.getMethod();//传入当前注解RecordOperateRecordOperate 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测试
控制台输出同步操作和异步操作