AOP切面编程实战

AOP(Aspect Oriented Programming,面向切面编程) 是一种编程范式,旨在通过分离横切关注点来提高程序的模块化和可维护性。
在传统的编程中,业务逻辑和其他非业务相关的关注点(如日志记录、事务管理、权限控制等)常常交织在一起,使得代码变得复杂且难以理解和维护。AOP 则将这些横切关注点从业务逻辑中分离出来,形成独立的模块,称为“切面”。
AOP 的主要概念包括:

  1. 切点(Pointcut):定义在程序中哪些连接点(例如方法执行、异常抛出等)需要应用切面逻辑。
  2. 通知(Advice):在切点处执行的具体逻辑,例如前置通知(在连接点之前执行)、后置通知(在连接点之后执行)、环绕通知(包围连接点执行)等。
  3. 切面(Aspect):切点和通知的组合。
    AOP 带来了许多好处,例如:
  4. 提高代码的可读性和可维护性,因为业务逻辑更加纯粹,不被非业务相关的代码干扰。
  5. 增强代码的复用性,横切关注点可以在多个地方重复使用。
    例如,在一个电商系统中,对于订单处理的方法,可能需要在其执行前后进行日志记录。使用 AOP,就可以将日志记录的逻辑定义为一个切面,然后应用到订单处理方法这个切点上,而无需在每个订单处理方法内部重复编写日志记录的代码。

实战

定义主链路操作

假设主链路中有一个订单服务,分为保存新建的订单和更新已有的订单,java代码如下:
订单服务

import org.springframework.stereotype.Service;  
  
@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;    }  
}

保存订单类

import lombok.Data;  
  
@Data  
public class SaveOrder {  
    private Long id;  
}

更新订单类

import lombok.Data;  
  
@Data  
public class UpdateOrder {  
    private Long orderId;  
}

定义日志类

想要将订单相关操作记录到日志中,需要定义日志类,包含订单id、订单描述、订单操作结果,java代码如下:

import lombok.Data;  
  
@Data  
public class OperateLogDO {  
  
    private Long orderId;  
  
    private String desc;  
  
    private String result;  
  
}

定义转换接口和实现类

因为不同订单的数据格式不同,这里定义转化接口,并将不同订单转换成相同的日志格式。

public interface Convert <PARAM>{  
  
    OperateLogDO convert(PARAM param);  
}

保存订单对应的转换类:

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;  
    }  
}

定义注解和切面类

在注解中声明操作类型(保存订单和更新订单的操作类型不同)和转换类

import java.lang.annotation.*;  
  
@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface RecordOperate {  
    String desc() default "";  
  
    Class<? extends Convert> convert();  
}

切面类中定义了切入点和统一操作方法,其中使用线程池实现异步执行aop操作,不影响主链路性能。通过签名获得注解,设置日志的相关参数。

import com.alibaba.fastjson.JSON;  
import org.aspectj.lang.ProceedingJoinPoint;  
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.util.concurrent.LinkedBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
  
@Aspect  
@Component  
public class OperateAspect {  
  
    /**  
     * 切点:在哪个方法(注解)处执行  
     * 通知(横切逻辑)  
     * 切面  
     */  
    @Pointcut("@annotation(com.sky.aop.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(()->{  
            try {  
                MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();  
                RecordOperate annotation = methodSignature.getMethod().getAnnotation(RecordOperate.class);  
  
                Class<? extends Convert> convert = annotation.convert();  
                Convert logConvert = convert.newInstance();  
                OperateLogDO operateLogDO = logConvert.convert(proceedingJoinPoint.getArgs()[0]);  
  
                operateLogDO.setDesc(annotation.desc());  
                operateLogDO.setResult(result.toString());  
  
                System.out.println("insert operateLog " + JSON.toJSONString(operateLogDO));  
            } catch (InstantiationException e) {  
                throw new RuntimeException(e);  
            } catch (IllegalAccessException e) {  
                throw new RuntimeException(e);  
            }  
        });  
        return result;  
    }  
}

调用

新建Application类,调用订单服务,会自动执行aop记录日志操作:

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.CommandLineRunner;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
  
@SpringBootApplication  
public class Application implements CommandLineRunner {  
  
    public static void main(String[] args) {  
        new SpringApplication(Application.class).run(args);  
    }  
  
    @Autowired  
    private OrderService orderService;  
  
    @Override  
    public 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);  
  
    }  
}

参考资料:
【【Java高级】你真的会切面编程么?技术专家实战演示!全是细节!】 https://www.bilibili.com/video/BV1oD4y1W7Lh/?share_source=copy_web&vd_source=b801836c05e38cbd2348169f8f2b95f5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值