1:问题分析:
业务表中的公共字段:
序号 | 字段名 | 含义 | 数据类型 |
1 | create_time | 创建时间 | datetime |
2 | create_user | 创建人id | bigint |
3 | update_time | 修改时间 | datetime |
4 | update_user | 修改人id | bigint |
问题:代码冗余、不便于后期维护
2:解决思路:
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint |
利用AOP面向切面编程
同时这里也用到了反射的思想
实现思路:
自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法:
编写一个注解类:
package com.sky.anno;
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//描述这个注解什么时候生效
@Target(ElementType.METHOD)//当前这个注解作用在什么地方
public @interface AutoFill {
OperationType value();
}
自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
package com.sky.aop;
import com.sky.anno.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Slf4j
@Component
@Aspect
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("@annotation(com.sky.anno.AutoFill)")
private void pt(){}
@Before("pt()")
public void AutoFill(JoinPoint joinPoint) throws Throwable{
log.info("自动填充字段");
//1:获取到当前被拦截的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法的签名对象
System.out.println(signature);
System.out.println(signature.getMethod());
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//2:获取到当前被拦截操作的参数---实体
Object[] args = joinPoint.getArgs();
Object entity = args[0];//约定将被拦截的参数放在第一位。
//3:准备要为这个实体赋的值
LocalDateTime now = LocalDateTime.now();//当前时间
Long id = BaseContext.getCurrentId();//id(通过ThreadLocal)
//4:为这个实体赋值(当前时间,和id)(通过反射来赋值)
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
if(operationType.equals(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);
//通过反射为对象赋值
setCreateTime.invoke(entity,now);
setUpdateTime.invoke(entity,now);
setCreateUser.invoke(entity,id);
setUpdateUser.invoke(entity,id);
} catch (Exception e) {
e.printStackTrace();
}
}else{//另一种情况就是Update
try {
setUpdateUser.invoke(entity, id);
setUpdateTime.invoke(entity,now);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
注意点:
1:在这个类上加上@Aspect,表明这是一个注解了类。
2:定义这个切入点
@Pointcut("@annotation(com.sky.anno.AutoFill)")
private void pt(){}
@Before("pt()")
因为这里的功能是在update和insert两个操作上,填充公共的字段,所以,可以考虑使用before这种通知类型。
3:AutoFill:
1:获取到当前被拦截的数据库操作类型
思考这个需求,要想获得作用在数据库操作类型
这个AutoFill是不是已注解的方式加在上面的,所以这个问题就转化为了获取对应方法上的注解对象,
想获取对应方法上的注解对象,那得先获取对应方法的对象(反射那一块的知识),问题又转化为了获取对应方法的对象,
想获取对应方法上的对象,需要通过连接点JoinPoint获取方法的签名对象,然后来获取,这里还得提一下:如果实际拦截的是一个方法的话,可以向下转型为MethodSignature
所以具体代码:
//1:获取到当前被拦截的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法的签名对象
System.out.println(signature);
System.out.println(signature.getMethod());
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
2:获取到当前被拦截操作的参数---实体
这个也是同理,想获得这个实体,我们可以通过JoinPoint获取参数的方法,不过我们规定这个实体对象的参数要放在第一位,因为我们后面直接调用args[0]。
//2:获取到当前被拦截操作的参数---实体
Object[] args = joinPoint.getArgs();
Object entity = args[0];//约定将被拦截的参数放在第一位。
3:准备要为这个实体赋的值
准备这个实体赋的值比较简单,时间的话一般都是LocalDateTime.now()就行
这个id需要用到之前的知识点:ThreadLocal
新增员工操作-CSDN博客,这一篇的最后代码优化部分有提到ThreadLocal。
//3:准备要为这个实体赋的值
LocalDateTime now = LocalDateTime.now();//当前时间
Long id = BaseContext.getCurrentId();//id(通过ThreadLocal)
4:为这个实体赋值(当前时间,和id)(通过反射来赋值)
为这个实体赋值,就是通过放射的方法了(invoke)
//4:为这个实体赋值(当前时间,和id)(通过反射来赋值)
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
if(operationType.equals(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);
//通过反射为对象赋值
setCreateTime.invoke(entity,now);
setUpdateTime.invoke(entity,now);
setCreateUser.invoke(entity,id);
setUpdateUser.invoke(entity,id);
} catch (Exception e) {
e.printStackTrace();
}
}else{//另一种情况就是Update
try {
setUpdateUser.invoke(entity, id);
setUpdateTime.invoke(entity,now);
} catch (Exception e) {
e.printStackTrace();
}
}
在 Mapper 的方法上加入 AutoFill 注解
总结:
这一个填充公共字段虽然不是业务里的操作,不过涉及到的知识点很多:特别是SpringAOP和Java的反射。