AOP(Aspect Oriented Programming,面向切面编程) 是一种编程范式,旨在通过分离横切关注点来提高程序的模块化和可维护性。
在传统的编程中,业务逻辑和其他非业务相关的关注点(如日志记录、事务管理、权限控制等)常常交织在一起,使得代码变得复杂且难以理解和维护。AOP 则将这些横切关注点从业务逻辑中分离出来,形成独立的模块,称为“切面”。
AOP 的主要概念包括:
- 切点(Pointcut):定义在程序中哪些连接点(例如方法执行、异常抛出等)需要应用切面逻辑。
- 通知(Advice):在切点处执行的具体逻辑,例如前置通知(在连接点之前执行)、后置通知(在连接点之后执行)、环绕通知(包围连接点执行)等。
- 切面(Aspect):切点和通知的组合。
AOP 带来了许多好处,例如: - 提高代码的可读性和可维护性,因为业务逻辑更加纯粹,不被非业务相关的代码干扰。
- 增强代码的复用性,横切关注点可以在多个地方重复使用。
例如,在一个电商系统中,对于订单处理的方法,可能需要在其执行前后进行日志记录。使用 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