业务需求
对比原单据和变更后的单据生成变更日志,即哪些字段发生了变更记录变更前和变更后的值
工具类
由于对比的字段较多,所以就想封装一个统一的工具类来使用,最初一版参考网上的一些资料封装了一个工具类,方法返回Map,key为属性名,value为一个存储变更前后值的list
/**
* 工具类:比较两个对象并获取其中不相等的字段
*/
public abstract class AbstractContrast {
/**
* 比较两个对象属性o1和o2是否相等 相等返回true(子类根据实际情况重写对比方法)
*
* @param o1
* @param o2
* @return
*/
protected boolean isEquals(Object o1, Object o2) {
return Objects.deepEquals(o1, o2);
}
/**
* 比较两个对象并获取其中不相等的字段
*
* @param obj1 对象1
* @param obj2 对象2
* @return Map key:属性名 value:[对象1的属性值,对象2的属性值]
*/
public Map<String, List<Object>> compareFields(Object obj1, Object obj2) {
return compareFields(obj1, obj2, null, null);
}
/**
* 比较两个对象并获取其中不相等的字段(设置对比过程包含的属性)
*
* @param obj1 对象1
* @param obj2 对象2
* @param includeArr 对比包含的属性
* @return Map key:属性名 value:[对象1的属性值,对象2的属性值]
*/
public Map<String, List<Object>> compareFieldsIncludeProp(Object obj1, Object obj2, String[] includeArr) {
return compareFields(obj1, obj2, includeArr, null);
}
/**
* 比较两个对象并获取其中不相等的字段(设置对比过程忽略的属性)
*
* @param obj1 对象1
* @param obj2 对象2
* @param ignoreArr 忽略对比的属性
* @return Map key:属性名 value:[对象1的属性值,对象2的属性值]
*/
public Map<String, List<Object>> compareFieldsIgnoreProp(Object obj1, Object obj2, String[] ignoreArr) {
return compareFields(obj1, obj2, null, ignoreArr);
}
/**
* 比较两个对象并获取其中不相等的字段
*
* @param obj1 对象1
* @param obj2 对象2
* @param includeArr 对比包含的属性
* @param ignoreArr 忽略对比的属性
* @return Map key:属性名 value:[对象1的属性值,对象2的属性值]
*/
private Map<String, List<Object>> compareFields(Object obj1, Object obj2, String[] includeArr, String[] ignoreArr) {
try {
Map<String, List<Object>> map = new HashMap<>(16);
//只有两个对象都是同一类型才有可比性
if (obj1.getClass() != obj2.getClass()) {
return map;
}
List<String> includeArrList = null;
List<String> ignoreList = null;
if (includeArr != null && includeArr.length > 0) {
includeArrList = Arrays.asList(includeArr);
}
if (ignoreArr != null && ignoreArr.length > 0) {
ignoreList = Arrays.asList(ignoreArr);
}
Class clazz = obj1.getClass();
//获取object的属性名称
PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
String propName = pd.getName();
//如果该属性不属于对比包含的属性或者属于忽略对比的属性就跳过该属性对比
if ((includeArrList != null && !includeArrList.contains(propName)) ||
(ignoreList != null && ignoreList.contains(propName))) {
continue;
}
//获取属性的get方法
Method readMethod = pd.getReadMethod();
//在obj1、obj2上调用get方法等同于获得obj1、obj2的属性值
Object o1 = readMethod.invoke(obj1);
Object o2 = readMethod.invoke(obj2);
//如果不相等放入Map
if (!isEquals(o1, o2)) {
map.put(propName, Arrays.asList(o1, o2));
}
}
return map;
} catch (Exception e) {
throw new RuntimeException("比对过程中发生异常", e);
}
}
}
第二版考虑到变更日志的相关字段,进行了改进:
- ChangeLog需要初始化相关值
- ChangeLog需要设置属性描述
- 设置一个ChangeLog的基类方便扩展
最终采用了使用注解标注字段来设置属性描述的方式
@Data
public class BaseChangeLog {
/**
* 变更单据ID
*/
protected Long documentId;
/**
* 属性名
*/
protected String propName;
/**
* 属性描述
*/
protected String propDesc;
/**
* 变更前
*/
protected String beforeChange;
/**
* 变更后
*/
protected String afterChange;
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangeLogDesc {
String value() default "";
}
/**
* 工具类:比较两个对象并获取其中不相等的字段
* 需要配合@ChangeLogDes注解进行使用
* 使用@ChangeLogDesc注解标注相应字段来设置属性的描述信息
* 只有使用@ChangeLogDesc注解标注的属性才会生成ChangeLog
*/
public abstract class AbstractContrast<T extends BaseChangeLog> {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* 比较两个对象属性o1和o2是否相等 相等返回true(子类根据实际情况重写对比方法)
*
* @param o1
* @param o2
* @return
*/
protected boolean isEquals(Object o1, Object o2) {
return Objects.deepEquals(o1, o2);
}
/**
* 初始化变更记录对象(需要子类根据实际情况重写)
*
* @return
*/
protected abstract T initChangeLog();
/**
* 比较两个对象并获取其中不相等的字段
*
* @param obj1 对象1
* @param obj2 对象2
* @return
*/
public List<T> compareFields(Object obj1, Object obj2) {
try {
List<T> changeLogList = new LinkedList<>();
//只有两个对象都是同一类型才有可比性
if (obj1.getClass() != obj2.getClass()) {
return changeLogList;
}
List<Field> fieldList = new ArrayList<>();
Class tempClass = obj1.getClass();
while (tempClass != null) {//当父类为null的时候说明到达了最上层的父类(Object类).
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass(); //得到父类,然后赋给自己
}
for (Field field : fieldList) {
//设置对象的访问权限,保证对private的属性的访问
field.setAccessible(true);
//只有使用@ChangeLogDesc注解标注的属性才会生成ChangeLog
ChangeLogDesc annotation = field.getAnnotation(ChangeLogDesc.class);
if (annotation == null) {
continue;
}
String propName = field.getName();
//获取obj1、obj2的属性值
Object o1 = field.get(obj1);
Object o2 = field.get(obj2);
//时间格式转换
if (o1 instanceof ZonedDateTime) {
o1 = ((ZonedDateTime) o1).format(formatter);
}
if (o2 instanceof ZonedDateTime) {
o2 = ((ZonedDateTime) o2).format(formatter);
}
//如果不相等构建ChangeLog放入集合中
if (!isEquals(o1, o2)) {
//初始化ChangeLog
T changeLog = initChangeLog();
//属性名
changeLog.setPropName(propName);
//属性描述
changeLog.setPropDesc(annotation.value());
//变更前
changeLog.setBeforeChange(o1 != null ? String.valueOf(o1) : "");
//变更后
changeLog.setAfterChange(o2 != null ? String.valueOf(o2) : "");
changeLogList.add(changeLog);
}
}
return changeLogList;
} catch (Exception e) {
throw new RuntimeException("比对过程中发生异常", e);
}
}
}
测试类:
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {
@ChangeLogDesc(value = "id")
private Integer id;
@ChangeLogDesc(value = "姓名")
private String name;
@ChangeLogDesc(value = "出生日期")
private ZonedDateTime birthDay;
}
@Data
public class StudentChangeLog extends BaseChangeLog {
}
public class Client {
public static void main(String[] args) {
AbstractContrast contrast = new AbstractContrast<StudentChangeLog>() {
@Override
protected StudentChangeLog initChangeLog() {
StudentChangeLog changeLog = new StudentChangeLog();
//初始化变更单据ID
changeLog.setDocumentId(1L);
return changeLog;
}
};
Student s1 = new Student(1, "小明", ZonedDateTime.now());
Student s2 = new Student(2, "小李", null);
List<StudentChangeLog> changeLogList = contrast.compareFields(s1, s2);
for (StudentChangeLog changeLog : changeLogList) {
System.out.print("单据ID为:" + changeLog.getDocumentId());
System.out.print(",属性名为:" + changeLog.getPropName());
System.out.print(",属性描述为:" + changeLog.getPropDesc());
System.out.print(",变更前为:" + changeLog.getBeforeChange());
System.out.print(",变更后为:" + changeLog.getAfterChange());
System.out.println();
}
}
}
执行结果如下:
单据ID为:1,属性名为:id,属性描述为:id,变更前为:1,变更后为:2
单据ID为:1,属性名为:name,属性描述为:姓名,变更前为:小明,变更后为:小李
单据ID为:1,属性名为:birthDay,属性描述为:出生日期,变更前为:2020-02-03,变更后为:null