一什么是AOP?
AOP又名Aspect Oriented Programming 意为 ‘面向切面编程’通过预编译和运行期间动态代理来实现程序功能的统一维护的一种技术。AOP思想是OOP(面向对象)的延续 在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP中的基本单元是 Aspect(切面),AOP是软件行业的热点,也是Spring框架中的一个重要内容,是函数式编程的一种延伸范式,
总结:这种在运行时生成代理对象来织入的,还可以在编译期、类加载期织入,动态地将代码在不改变原有的逻辑情况下切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
二AOP实现公共字段自动填充
在开发中,我们经常需要在数据库插入或更新操作时自动填充一些公共字段,例如 createTime、createUser、updateTime 和 updateUser。这种重复性的代码逻辑如果直接写在业务层中,不仅冗余,还会增加代码的维护成本。为了解决这一问题,可以使用 AOP(面向切面编程) 的思想,使用一个自定义的注解就能够轻松实现公共字段的自动填充。
2.1导入AOP依赖
在 Maven 的 pom.xml 文件中添加AOP的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2创建自定义注解
2.2.1 定义枚举
用来区分插入和更新操作
public enum OperationType {
//更新操作
UPDATE,
//插入操作
INSERT
}
2.2.2 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
2.2.3 使用
插入函数传INSERT枚举, 更新函数传 UPDATE枚举.用于给后续通知逻辑中区分操作类型
@AutoFill(value = OperationType.INSERT)
void insertUser(User user);
@AutoFill(value = OperationType.UPDATE)
void updateUser(User user);
2.3 定义切面
2.3.1 声明切面类
@Aspect
@Component
public class AutoFillAspect {
}
(1)使用@Aspect声明该类是一个切面类,以此来使用相应的方法
(2)使用@Component将其加入spring容器管理
2.3.2 定义切入点
@Aspect
@Component
public class AutoFillAspect {
//切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")//如果不加@annotation,那么拦截mapper包下所有的方法,加了就拦截这个包下的加了这个注解的方法
public void autoFillPointCut(){}
}
(1)@Pointcut
注解定义切入点位置 (这里的切入点是mapper包下且带有@AutoFill注解的函数;其他包下或者不带@AutoFill注解的就不会被拦截) , 这样就可以精确定位想要拦截的方法.
(2)public void autoFillPointCut(){}
空函数用来匹配接下来定义的通知.
2.3.3 定义通知
我们实现AOP的主要逻辑是在定义切面上体现.
在公共字段自动填充这个场景中,我们需要在被拦截函数执行前将函数中的参数对象取出来,填充里面的公共字段,再还给该函数,因此我们需要使用前置通知
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) { ... }
(1)@Before(“autoFillPointCut()”)用来指明前置通知,其他场景中可能会使用到其他通知类型.
(2)参数JoinPoint 提供了关于当前被拦截函数的一些信息,包括被拦截的函数的名称,签名,函数的参数列表等等信息,我们就是通过这个JoinPoint参数来得到被拦截的函数上的信息并执行我们的逻辑.
@Aspect
@Component
public class AutoFillAspect {
//切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")//如果不加@annotation,那么拦截mapper包下所有的方法,加了就拦截这个包下的加了这个注解的方法
public void autoFillPointCut(){}
@Before("autoFillPointCut()") //表示这个autoFillPointCut()切入点检测到的方法在执行前会进入这里执行前置方法
public void autoFill(JoinPoint joinPoint){ //JoinPoint 参数用于获取有关被拦截方法的信息,如方法签名、注解等。
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature= (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();
//获取到当前被拦截方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length ==0)
return;
Object entity = args[0];//获得第一个参数
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType==OperationType.INSERT){
//为四个公共字段赋值
try {
Method setCreateTime =entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
Method setCreateUser =entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
Method setUpdateTime =entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);//根据实体通过反射找到对应的set方法
Method setUpdateUser =entity.getClass().getDeclaredMethod("setUpdateUser",Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为两个公共字段赋值
try {
Method setUpdateTime =entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
Method setUpdateUser =entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(1)通过joinPoint获取被拦截的函数的信息
(2)通过反射来为被拦截函数的参数对象中的公共字段赋值
2.4使用
User实体类:
public class User{
private String id;
//名称
private String name;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
//创建人
private Long createUser;
//修改人
private Long updateUser;
}
在mapper的方法上填上对应的注解,就能做到相应字段自动填充.
@Mapper
public interface UserMapper {
@AutoFill(value = OperationType.INSERT)
void insertUser(User user);
}