記錄變更日誌:比較兩個對象並獲取其中不相等的字段

業務需求

對比原單據和變更後的單據生成變更日誌,即哪些字段發生了變更並且記錄變更前和變更後的值

工具類

由於對比的字段較多,所以就想封裝一個統一的工具類來使用,最初一版參考網上的一些資料封裝了一個工具類,方法返回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

參考:

https://github.com/dadiyang/equator

https://blog.csdn.net/dadiyang/article/details/88782898?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章