java中实现公共字段自动填充功能的问题分析与实现
目录
前言·问题分析
问题分析:代码冗余,不便于后期维护
· 在我们的很多业务中存在一些公共的字段,当我们需要对这些字段进行赋值操作的时候,会重复且平凡的执行赋值语句,导致我们的java代码中出现很多的冗余代码。如果我们后期的表结构发生变化,那我们的这些代码都需要进行改动,不便于代码的后期维护。
一、实现思路
· 首先明确公共字段属于哪种操作类型,我们什么时候去操作它,例如1、2两个字段见明知意是我们执行 insert 语句时需要进行赋值操作,2、4两个字段是我们在执行 insert 和 update 语句时需要进行操作赋值操作
· 以上公共字段在我们的java种可以统一来处理 也就是通过 aop 切面编程来处理,因为对公共字段的填充的处理在我们的持久层也就是 mapper 层中进行,在 mapper 层执行 insert 和 update 来插入和更新数据。此时可以通过切面来同意拦截我们的 mapper 层,来统一为我们的公共字段进行赋值,为 mapper 层的方法来加入我们自定义的注解之后,就可以对当前方法进行统一拦截并进行统一赋值。
二、实现步骤
1.自定义注解AutoFill
· 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法:
package com.sky.annotation;
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;
/**
* 自定义注解,用于某个方法上需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型,INSERT UPDATE
OperationType value();
}
· 创建annotation包(注意与mapper包同级)用于存放自定义注解AutoFill(使用Annotation注解关键字声明)
· 注解@Target(ElementType.METHOD) 用于指定AutoFill只能加在方法上
· 注解Retention(RetentionPolicy.RUNTIME) 固定写法
· OperationType 为声明的枚举类 具体声明如下:
package com.sky.enumeration;
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
· 创建enumeration包(注意与mapper包同级)用于存放自定义枚举类OperationType(使用enum枚举关键字声明)
2.自定义切面类AutoFillAspect
·自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段进行赋值
package com.sky.aspect;
import com.sky.annotation.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;
/**
* 自定义切面,实现公共字段自动填充逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 定义切入点,execution(* com.sky.mapper.*.*(..))指定拦截com.sky.mapper包下的所有方法
* 并且只拦截 @annotation(com.sky.annotation.AutoFill) 带AutoFill注解的方法
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段填充
* @param joinPoint
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取当前被拦截的方法上的数据库操作类型
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();//当前登录用户的id
//根据当前不同的操作类型,把对应的属性通过反射进行赋值
if (operationType == OperationType.INSERT){
try {
Method setCreatTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreatUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreatTime.invoke(entity,now);
setCreatUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
log.info("INSERT公共字段填充失败");
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) {
log.info("UPDATE公共字段填充失败");
throw new RuntimeException(e);
}
}
}
}
· 创建aspect包(注意与mapper包同级)用于存放自定义切面类AutoFillAspect(使用class类关键字声明)
· 注意:根据不同操作类型反射赋值的时候,获取实体中的方法名称和方法类型的时候,方法名我这里使用了一个常量类,具体的方法名在常量类里(此处也可以直接定义方法名)
3.在mapper的方法上加入AutoFill注解即可
· @AutoFill(OperationType.INSERT)
总结
技术要点:枚举、注解、AOP、反射