在使用 SpringBoot 框架开发项目时,经常会遇到 「创建时间」「修改时间」等公共字段,这些字段每次都需要我们手动去设置,十分麻烦。
本文使用 SpringBoot 中的切面功能来实现这些公共字段的自动填充
定义注解#
首先我们定义一个注解用于标记哪些方法需要实现自动填充
/**
* 标识需要自动填充公共字段的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
/**
* 数据库操作类型
* @return
*/
OperationType value();
}java@Target(ElementType.METHOD) 标记该注解用于方法上面
@Retention(RetentionPolicy.RUNTIME) 指定注解在运行阶段可用
OperationType value(); 函数指定注解需要指定一个参数 value,值为 OperationType 类型,OperationType是定义的一个枚举类,用于指定数据库操作的类型
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}java定义切面类#
切入点#
首先使用切入点表达式标记这个切面类会在哪些方法上执行
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotations.AutoFill)")
public void autoFillPointCut(){}java该表达式说明切入点是 mapper 包下的任意函数,但是需要使用了上述定义的 AutoFill 注解
通知方法#
使用前置通知,在 mapper 函数执行之前,完成字段自动填充过程
首先, 我们需要获取到数据库操作类型,使用 joinPoint.getSignature() 先获取需要填充字段的方法的签名,然后利用 java 的反射机制获取到该方法上的注解对象 signature.getMethod().getAnnotation(AutoFill.class), 从而获取到该注解对象携带的数据库操作类型。
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取方法注解对象
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);
// 获取数据库操作类型
OperationType op = annotation.value();java然后获取该方法的参数,即我们需要填充字段的实体对象, 我们约定实体对象放在方法的第一个参数。
// 获取需要填充的参数
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0)
return;
// 获取需要填充的实体对象, 约定实体对象放在方法的第一个参数
Object entity = args[0];java然后根据数据库操作类型判断需要填充哪些字段,利用反射获取到实体对象中相应字段的 set 方法并 invoke 该方法设置相应字段的值。
// 给参数赋值
if(op == OperationType.INSERT){
// 插入
try {
entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setCreateUser", Long.class).invoke(entity, currentId);
entity.getClass().getDeclaredMethod("setUpdateUser", Long.class).invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else if(op == OperationType.UPDATE){
// 更新
try {
entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setUpdateUser", Long.class).invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}java完整代码#
/**
* 自动填充切面类
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotations.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知方法实现自动填充
* @param joinPoint
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取方法注解对象
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);
// 获取数据库操作类型
OperationType op = annotation.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(op == OperationType.INSERT){
// 插入
try {
entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setCreateUser", Long.class).invoke(entity, currentId);
entity.getClass().getDeclaredMethod("setUpdateUser", Long.class).invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else if(op == OperationType.UPDATE){
// 更新
try {
entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class).invoke(entity, now);
entity.getClass().getDeclaredMethod("setUpdateUser", Long.class).invoke(entity, currentId);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}java使用#
要使用自动填充功能,只需要在 mapper 方法上加上 @AutoFill 注解即可
@AutoFill(value = OperationType.UPDATE)
void update(Category category);java