Aop切面编程

学习视频

一、定义模型:订单保存模型,订单更新模型,业务层,日志模型

订单保存模型

/**
 * @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实现策略

具体代理实现流程

  1. 定义切入点
  2. 横切逻辑
  3. 植入
    在业务层执行不同的业务操作时,通过环绕切面在方法执行之前和之后做操作,通过切入点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测试

在这里插入图片描述在这里插入图片描述
控制台输出同步操作和异步操作
在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP切面编程是一种通过配置的方式,实现在Spring开发中的AOP功能。切面由切点和增强组成,切点用于定义在哪些连接点上应用增强,而增强指的是在连接点上要执行的逻辑操作。Spring的AOP强大之处在于不需要通过代码,只需要使用注解或XML配置就可以完成相同的功能。 在学习Spring开发AOP面向切面编程时,需要导入相应的jar包,例如com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar。切面编程是一种实现横切关注点的重用和集中管理的技术,它可以让我们更加方便地实现一些横切关注点,如日志记录、事务管理等,并将其与业务逻辑分离开来,提高代码的复用性和可维护性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [spring AOP切面编程](https://blog.csdn.net/weixin_43525993/article/details/107902045)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SpringAOP切面编程依赖jar包.rar](https://download.csdn.net/download/weixin_44888416/12374630)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值