工作中接了一个这样的需求:进行修改操作时,记录一个修改日志表。需要记录改动过的属性,原来的值,现在的值等。此功能有一个亮点,接收的参数为diff(T a, T b)都为泛型,以泛型的方式做处理之前的开发中确实用的比较少。
解决方式如下:
public class DiffUtils {
private static final Map<Class<?>, Map<String, Field>> DIFF_PROPERTY_CACHE = new ConcurrentReferenceHashMap<>();
/**
* 比较两个相同类型的对象属性之间的差异
*
* @param a 对象A
* @param b 对象B
* @param <T> 对象类型
* @return 差异列表
* @throws IllegalAccessException
*/
@SneakyThrows
public static <T> List<DiffResult> diff(T a, T b) {
if (equals(a, b)) {
return Collections.EMPTY_LIST;
}
// 获取并缓存一个类中需要对比的字段, 并存入map集合。
Map<String, Field> cachedDiffPropertyMap = DIFF_PROPERTY_CACHE.computeIfAbsent((a != null ? a : b).getClass(),
clazz -> {
Map<String, Field> diffPropertyMap = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String name = field.getName();
diffPropertyMap.put(name, field);
}
return diffPropertyMap;
});
// 新老对象,属性值进行对比,并将对比结果,不同的进行返回
List<DiffResult> diffResultList = new ArrayList<>();
for (Map.Entry<String, Field> fieldEntry : cachedDiffPropertyMap.entrySet()) {
Field field = fieldEntry.getValue();
// field.get(a) 获取a对象field这个属性名的属性值!
Object aValue = a == null ? null : field.get(a);
Object bValue = b == null ? null : field.get(b);
if (!equals(aValue, bValue)) {
diffResultList.add(new DiffResult(field.getName(), fieldEntry.getKey(), aValue, bValue));
}
}
return diffResultList;
}
/**
* 比较两个对象是否相同
*
* @param a 对象A
* @param b 对比B
* @return 相同返回true,不同返回false
*/
public static boolean equals(Object a, Object b) {
if (a == null) {
return b == null;
}
if (a == b) {
return true;
}
return a.equals(b);
}
}
}
@Data
@AllArgsConstructor
public class DiffResult {
/**
* 字段名称
*/
private String fieldName;
/**
* 对比项名称
*/
private String propertyName;
/**
* 老对象的值
*/
private Object oldValue;
/**
* 新对象的值
*/
private Object newValue;
}
测试代码:
public static void test() {
SysUser sysUser = new SysUser(1L);
sysUser.setNickName("张三");
sysUser.setRoleId(333L);
sysUser.setSex("男");
SysUser sysUser1 = new SysUser(2L);
sysUser1.setNickName("李四");
sysUser1.setRoleId(666L);
sysUser1.setSex("女");
List<DiffResult> diff = ChangeLogUtils.diff(sysUser, sysUser1);
for (DiffResult diffResult : diff) {
System.out.println(diffResult);
}
}
控制台输出结果:
拓展:上述为 对比SysUser所有属性,如果想要控制部分属性对比,部分不对比可以增加注解。
@DiffProperty注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 对比项属性注解
*
* 只有标记了此注解的属性才会进行对比
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DiffProperty {
/**
* 对比项名称
* <p>
* 如果不指定,则取字段的名称
*
* @return
*/
String name() default "";
}
修改获取对比字段时的代码,增加注解判断。然后在需要对比的实体类属性上增加@DiffProperty。即可实现部分字段对比。
// 获取并缓存一个类中需要对比的字段
Map<String, Field> cachedDiffPropertyMap = DIFF_PROPERTY_CACHE.computeIfAbsent((a != null ? a : b).getClass(),
clazz -> {
Map<String, Field> diffPropertyMap = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
DiffProperty diffProperty = field.getAnnotation(DiffProperty.class);
if (diffProperty != null) {
field.setAccessible(true);
String name = diffProperty.name();
if (name.isEmpty()) {
name = field.getName();
}
diffPropertyMap.put(name, field);
}
}
return diffPropertyMap;
});