面向AOP切面编程 – 应用篇
前提:
自定义注解注意项
自定义注解中的参数:不是方法,而是参数。
这样写是因为自定义注解中的参数的命名要求,参数类型 + 参数名 + ();
如果只有一个参数 当使用注解的时候,可以将参数名省略
@AutoFill(value = OperationType.INSERT) --> @AutoFill(OperationType.INSERT)
/**
* 用于标识需要进行公共字段自动填充的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();//参数
}
@Mapper
public interface CategoryMapper {
@AutoFill(OperationType.INSERT)
void updateDate(Category category);
}
@Mapper
public interface CategoryMapper {
@AutoFill(value = OperationType.INSERT)
void updateDate(Category category);
}
创建自定义注解:
/**
* 用于标识需要进行公共字段自动填充的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();//参数
}
创建枚举类:
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
背景:
当使用sql语句进行插入、更新时,每当编写这两个sql语句的逻辑处理层都需要手动填充插入时间、更新时间、插入操作用户、更新操作用户字段。
解决:
创建自定义注解:用于标识哪些sql语句需要被拦截做统一的广播处理
准备工作,创建自定义注解,创建枚举类,创建切面(切入点和通知组成)
代码:
/**
* 自定义切面类 实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
//切入点 + 通知 = 切面
/**
* *:返回值是所有的
* com.sky.mapper:包
* .*:所有的类
* .*:所有的方法
* (..):匹配所有的参数类型
*/
//切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {
}
/**
* 前置通知(在执行sql语句之前进行通知操作),在通知中进行公共字段的赋值
*/
//通知
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {//连接点(JoinPoint joinPoint):获取当前被拦截的方法以及方法的参数
log.info("字段开始自动填充");
//1.判断被拦截到的方法是insert还是update
//获得签名对象 但是该对象是接口,被拦截到的是方法,所以要向下转型(转成子接口).
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法上的注解对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
//获取数据库操作类型
OperationType operationType = autoFill.value();
//2.获取方法的参数 约定俗成 要想实现自动填充功能,方法第一个参数要是实体对象
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
Object entity = args[0];
//3.为实体对象准备数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//4.通过反射进行赋值
if (operationType == OperationType.INSERT) {
//getClass() 返回此 Object 的运行时该对象的类. 该方法返回一个Class对象, 可以通过该对象可以获取某个类的相关信息, 如构造方法 属性 方法 等.
//getDeclaredMethod() 返回 一个Method对象,它反映此Class对象所表示的类或接口的指定已声明方法
/**
* 补充在 Java 中,getDeclaredMethod() 方法用于获取当前类中声明的指定名称和参数类型的方法。
* 参数说明:
* String name:这是一个字符串类型的参数,用于指定要获取的方法的名称。比如,如果您要获取名为 calculateSum 的方法,那么这里就应传入 "calculateSum" 。
* 例如:假设您有一个方法叫 processData ,您就传入 "processData" 来获取该方法。
* Class<?>... parameterTypes:这是一个可变参数,用于指定方法的参数类型。
* 比如,如果要获取的方法接收两个参数,分别为 int 类型和 String 类型,那么可以这样写:Class<?>[] parameterTypes = {Integer.class, String.class} 。
* 再比如,如果方法没有参数,那么这里就传入一个空的参数列表即可。
*/
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class;
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class;
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//为公共字段赋值 通过反射
setCreateTime.invoke(entity, now);
setUpdateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
} 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) {
throw new RuntimeException(e);
}
}
}
}