前言
很多时候,我们需要对两个对象做diff,如果字段一多,比较起来相当的麻烦,利用反射,可以快速的实现一些diff比较,尽量性能不好,这是空间换时间的一些做法。
正文
核心实现如下:
/**
* modify field diff util
* @author Cao Yong
* @date 2021-05-28 9:48:12
*/
public class ModifyFieldsDiffUtil {
/**
* get modify field diffs
* @param before before modify DTO
* @param after after modify DTO
* @param diffs diff list
* @param customerClass customer class, need adding all customer diff object
* @param <T> class type that diff
*/
public static <T> void diff(T before, T after, List<DiffContent> diffs, Class<?>... customerClass) {
//can not be null for all
if (before == null && after == null) {
return;
}
//get all fields for none null
T allFieldsObj = before == null ? after : before;
List<Field> finalFields = getClassAllFields(allFieldsObj);
//iterator field
for (Field declaredField : finalFields) {
getDiffInfo(before, after, diffs, declaredField, customerClass);
}
}
/**
* get class fields and supper class fields
* @param obj object
* @return all fields
*/
private static List<Field> getClassAllFields(Object obj) {
Field[] superFields = null;
Class<?> superclass = obj.getClass().getSuperclass();
//get supper declared fields
if (superclass != null) {
superFields = superclass.getDeclaredFields();
}
//get declared fields
Field[] fields = obj.getClass().getDeclaredFields();
//all fields
Field[] finalFields;
if (superFields != null && superFields.length > 0) {
finalFields = new Field[superFields.length + fields.length];
//copy all super field
System.arraycopy(superFields, 0, finalFields, 0, superFields.length);
//copy all self field
System.arraycopy(fields, 0, finalFields, superFields.length, fields.length);
} else {
finalFields = fields;
}
return Arrays.stream(finalFields).filter(field -> field.isAnnotationPresent(DiffField.class)).collect(Collectors.toList());
}
/**
* get diff info
* @param before diff before object
* @param after diff after object
* @param diffs diffs ArrayList
* @param declaredField declared field
* @param customerClass customer class, need adding all customer diff object
*/
private static <T> void getDiffInfo(T before, T after, List<DiffContent> diffs, Field declaredField, Class<?>... customerClass) {
final String uId = "serialVersionUID";
//ignore serialVersionUID
if (uId.equals(declaredField.getName())) {
return;
}
Object beforeInvoke = null;
Object afterInvoke = null;
if (before != null) {
try {
//get diff before object by invoke
beforeInvoke = new PropertyDescriptor(declaredField.getName(), before.getClass()).getReadMethod().invoke(before);
} catch (Exception ignored) {
}
}
if (after != null) {
try {
//get diff after object by invoke
afterInvoke = new PropertyDescriptor(declaredField.getName(), after.getClass()).getReadMethod().invoke(after);
} catch (Exception ignored) {
}
}
//adding diffs
addingDiff(diffs, declaredField, beforeInvoke, afterInvoke, customerClass);
if (beforeInvoke != null && afterInvoke != null) {
if (beforeInvoke instanceof ArrayList) {
List<?> beforeList = (ArrayList<?>) beforeInvoke;
List<?> afterList = (ArrayList<?>) afterInvoke;
//break when size not equals
if (afterList.size() != beforeList.size()) {
return;
}
int index = 0;
for (Object beforeObj : beforeList) {
//avoid recursive reference
if (beforeObj.getClass() == before.getClass()) {
throw new RuntimeException("Can not recursive depend");
}
//recursive call
diff(beforeObj, afterList.get(index), diffs, customerClass);
index++;
}
}
}
}
/**
* adding diffs
* @param diffs diffs ArrayList
* @param declaredField declared field
* @param beforeInvoke before invoke value
* @param afterInvoke after invoke value
* @param customerClass customer class, need adding all customer diff object
*/
private static <T> void addingDiff(List<DiffContent> diffs, Field declaredField, T beforeInvoke, T afterInvoke, Class<?>... customerClass) {
//get field annotation
DiffField annotation = declaredField.getAnnotation(DiffField.class);
//adding string diff
if (String.class.getName().equals(declaredField.getType().getName())) {
String beforeField = beforeInvoke == null ? "" : (String) beforeInvoke;
String afterField = afterInvoke == null ? "" : (String) afterInvoke;
if (!beforeField.equals(afterField)) {
DiffContent diff = new DiffContent();
diff.setModifyField(annotation == null ? declaredField.getName() : annotation.value());
diff.setModifyField(declaredField.getName());
diff.setBeforeModification(beforeField);
diff.setAfterModification(afterField);
diffs.add(diff);
}
}
//adding integer diff
if (Integer.class.getName().equals(declaredField.getType().getName())) {
Integer beforeField = beforeInvoke == null ? 0 : (Integer) beforeInvoke;
Integer afterField = afterInvoke == null ? 0 : (Integer) afterInvoke;
if (!beforeField.equals(afterField)) {
DiffContent diff = new DiffContent();
diff.setModifyField(annotation == null ? declaredField.getName() : annotation.value());
diff.setBeforeModification(String.valueOf(beforeInvoke));
diff.setAfterModification(String.valueOf(afterInvoke));
diffs.add(diff);
}
}
//adding long diff
if (Long.class.getName().equals(declaredField.getType().getName())) {
Long beforeField = Objects.isNull(beforeInvoke) ? 0 : (Long) beforeInvoke;
Long afterField = Objects.isNull(afterInvoke) ? 0 : (Long) afterInvoke;
if (!Objects.equals(beforeField, afterField)) {
DiffContent diff = new DiffContent();
diff.setModifyField(annotation == null ? declaredField.getName() : annotation.value());
diff.setBeforeModification(String.valueOf(beforeInvoke));
diff.setAfterModification(String.valueOf(afterInvoke));
diffs.add(diff);
}
}
//adding customer class diff
if (customerClass != null && customerClass.length > 0) {
List<String> customerClassNames = Arrays.stream(customerClass).map(Class::getName).collect(Collectors.toList());
if (customerClassNames.stream().anyMatch(declaredField.getType().getName()::equals)) {
//recursive call
diff(beforeInvoke, afterInvoke, diffs, customerClass);
}
}
}
}
定义diff返回对象
/**
*
* @author Cao Yong
* @date 2021-05-28日 9:52:23
*/
@Data
public class DiffContent implements Serializable {
private static final long serialVersionUID = 646098769625590576L;
private String modifyField;
private String beforeModification;
private String afterModification;
}
还可以通过自定义注解来标识字段
/**
*
* @author Cao Yong
* @date 2020-05-28 21:05:23
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DiffField {
/**
* parent value
* @return parent value
*/
String parentValue() default "";
/**
* value
* @return value
*/
String value() default "";
}
注解使用示例:
/**
* 主键ID
*/
@DiffField(parentValue = "资料详情", value = "主键id")
private Long id;
测试代码示例:
/**
*
* @author Cao Yong
* @date 2021-05-28日 13:33:23
*/
public class DiffMainTest {
public static void main(String[] args) throws Exception {
DetailDto detail1 = new DetailDto()
detail1.setName("test name1");
DetailDto detail2 = new DetailDto()
detail2.setName("test name2");
List<DiffContent> diffs = new ArrayList<>();
//RecordDto是对象中自己定义的对象
ModifyFieldsDiffUtil.diff(detail1, detail2, diffs, RecordDto.class);
System.out.println("diffs:{}===============" + diffs);
}
}