注意:本项目未发布到maven中央仓库,需要手动添加到本地仓库 或者 到私有仓库中使用。
比较对象差异工具类。主要用于比较同一个类的两个对象属性值的差异。
此工具是建立在
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>中的DiffBuilder基础上进行的重新开发和封装。 主要是解决一些bug.和加入了一些缓存提高比较时频繁反射的性能。同时增加了一些新特性。
使用 @DiffIgnore 注解可以忽略某个属性的比较。
@DiffIgnore
private String name;调用工具类DiffUtils 时传递排除比较的字段名。
DiffResult diffResult = DiffUtils.diff(obj1, obj2, "name");使用 @DiffCompare 注解可以自定义某个属性的比较器。
自定义比较器需要实现 DiffComparable 接口。
public class MyComparator implements DiffComparable {
@Override
public boolean diff(Object obj1, Object obj2) {
return obj1.equals(obj2);
}
}如果自定义比较器可以使用无参构造实例化,可以不用手动注册到工厂中。否则用户需要自定义调用com.openquartz.javaobjdiff.DiffComparableFactory.register方法注入到工厂中。
使用自定义比较器
@DiffCompare(using = MyComparator.class)
private String name;库内置了几个可直接复用的比较器,无需自己实现:
DefaultDiffComparator:默认比较器,使用equals比较。BigDecimalEffectiveDiffComparator:BigDecimal忽略尾零的有效值比较,1.0与1.00视为相等。
@DiffCompare(using = BigDecimalEffectiveDiffComparator.class)
private BigDecimal money;支持自定义成员变量需要使用注解标识。会自动解析到属性
可以加注解@DiffBean来标识。
public class User {
private String name;
private int age;
private Date birthday;
@DiffBean
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setAddress(Address address) {
this.address = address;
}
public Address getAddress() {
return this.address;
}
}@DiffBean注解也支持加在 Collection及其子类上。并支持到Collection的元素类型为自定义类型
例如:
@DiffBean
private List<Packet> packetList;
@DiffBean
private List<Integer> friendIdList;使用 @DiffAlias 注解可以自定义某个属性的别名。
@DiffAlias(alias = "用户名")
private String name;输出报告中会显示别名。否则默认使用字段属性名。
使用 @DiffFormat 注解可以自定义某个属性的格式化。在输出结果中展示格式化后的数据。
@DiffFormat(pattern = "yyyy-MM-dd", using=DateTimeDiffFormatter.class)
private Date birthday;也可以自定义格式化器。需要实现接口:com.openquartz.javaobjdiff.DiffFormatter
例如:
public class DateTimeDiffFormatter implements DiffFormatter {
@Override
public String format(Object obj) {
if (StringUtils.isBlank(pattern)) {
pattern = DEFAULT_FORMATTER_PATTERN;
}
if (Objects.isNull(value)) {
return null;
}
if (value instanceof Date) {
return DateFormatUtils.format((Date) value, pattern);
}
if (value instanceof TemporalAccessor) {
return DateTimeFormatter.ofPattern(pattern).format((LocalDateTime) value);
}
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}
}使用场景:自定义类型、枚举类型等。可自己实现自定义格式化方式。
使用示例代码详见:com.openquartz.javaobjdiff.test.DiffUtilTest
对于手机号、邮箱、身份证号等敏感字段,在输出差异结果时支持自动脱敏展示,避免敏感信息在 diff 结果中明文泄露。
支持以下三种内置脱敏注解:
| 注解 | 适用字段 | 脱敏规则 | 示例 |
|---|---|---|---|
@EmailDataMask |
邮箱 | 前缀仅保留首字母,其余用 * 替代,保留 @ 及域名 |
[email protected] → ***********[email protected] |
@MobileDataMask |
手机号 / 座机 | 手机号保留前 3 位与后 4 位;座机保留区号与后 4 位 | 17000000056 → 170****0056;010-12345678 → 010-****5678 |
@IdCardDataMask |
身份证号 | 保留前 6 位与后 4 位(仅对 18 位生效) | 321556199301152110 → 321556********2110 |
用法示例:
public class User {
@MobileDataMask
private String mobile;
@EmailDataMask
private String email;
@IdCardDataMask
private String idCard;
}diff 输出示例:
User[mobile=170****0056, email=***********[email protected], idCard=321556********2110] -> User[mobile=171****0056, email=*********[email protected], idCard=301556********2110]
注意:脱敏仅作用于
DiffResult.toString()/Diff.getFormatValue()等输出展示环节,字段的比较逻辑仍基于原始值。两个对象该字段原始值不同,即使脱敏后的展示串相同,仍会被判定为有差异。
DiffUtils.diff(...) 返回格式化的字符串;如果需要以编程方式获取结构化结果,使用 DiffUtils.diffResult(...):
DiffResult<Person> result = DiffUtils.diffResult(person1, person2);
boolean diff = result.isDiff(); // 是否存在差异
int count = result.getNumberOfDiffs(); // 差异数量
List<Diff<?>> diffs = result.getDiffs(); // 差异列表(不可修改)
for (Diff<?> d : result) { // DiffResult 实现了 Iterable<Diff<?>>
String field = d.getFieldName(); // 字段路径,如 address.country
String name = d.getActualName(); // 展示名(别名优先)
Object left = d.getLeft(); // 左侧(旧)值
Object right = d.getRight(); // 右侧(新)值
Object formatLeft = d.getFormatValue(left); // 格式化/脱敏后的展示值
}// 为输出中的所有字段添加前缀
String diff = DiffUtils.diff(source, target, "user", "excludeField1", "excludeField2");
// 使用自定义 ToStringStyle
String diff = DiffUtils.diff(source, target, new MyToStringStyle(), "excludeField1");static、transient字段不参与比较。- 字段反射结果按
Class缓存,频繁比较同一类型的对象时性能更好。 - 默认使用
equals/ 引用相等进行比较,可通过@DiffIgnore、@DiffCompare、@DiffBean、@DiffFormat等注解覆盖默认行为。