是的,Java 生态中有多个开源工具可以用于比较(diff)同一类的两个对象之间的数据差异。你已经熟悉 [`equator`](https://github.com/yusaint/Equator),这里介绍一些其他常见的开源 diff 库: ## 1. **Java Object Differ** - **GitHub**: [https://github.com/SQiShER/java-object-diff](https://github.com/SQiShER/java-object-diff) - **特点**: - 递归比较对象的字段,支持集合、Map 及嵌套对象。 - 可生成详细的 `DiffNode`,提供变更的字段、值、类型等信息。 - 允许自定义比较器和过滤规则。 - **示例**: ```java DiffNode diff = ObjectDifferBuilder.buildDefault().compare(oldObj, newObj); diff.visit((node, visit) -> { System.out.println("字段: " + node.getPath() + " 变更: " + node.getState()); }); ``` --- ## 2. **Javers** - **GitHub**: [https://github.com/javers/javers](https://github.com/javers/javers) - **特点**: - 主要用于对象版本控制和审计,支持生成对象的历史变更记录。 - 支持深度对象比较,能够比较集合、嵌套对象。 - 提供 SQL 数据库集成,方便存储历史变更。 - **示例**: ```java Javers javers = JaversBuilder.javers().build(); Diff diff = javers.compare(oldObj, newObj); System.out.println(diff); ``` --- ## 3. **Apache Commons Lang - EqualsBuilder & DiffBuilder** - **GitHub**: [https://github.com/apache/commons-lang](https://github.com/apache/commons-lang) - **特点**: - 适用于简单对象的比较,支持字段级别的 `equals()` 方法。 - `DiffBuilder` 可以用于生成详细的差异。 - **示例**: ```java DiffResult diff = new DiffBuilder(oldObj, newObj, ToStringStyle.SHORT_PREFIX_STYLE) .append("name", oldObj.getName(), newObj.getName()) .append("age", oldObj.getAge(), newObj.getAge()) .build(); System.out.println(diff); ``` --- ## 4. **zjsonpatch(适用于 JSON 格式的对象)** - **GitHub**: [https://github.com/flipkart-incubator/zjsonpatch](https://github.com/flipkart-incubator/zjsonpatch) - **特点**: - 适用于 JSON 格式的对象,能够生成 JSON Patch 格式的差异。 - 适合用于 Web API、序列化对象的比较。 - **示例**: ```java ObjectMapper mapper = new ObjectMapper(); JsonNode before = mapper.valueToTree(oldObj); JsonNode after = mapper.valueToTree(newObj); JsonNode patch = JsonDiff.asJson(before, after); System.out.println(patch); ``` --- ## 适用场景对比 |库|适用场景|深度比较|输出格式|适用于| |---|---|---|---|---| |**Java Object Differ**|结构化对象差异|✅|`DiffNode` 树|复杂对象| |**Javers**|版本对比、审计|✅|`Diff` 结果|领域建模| |**Apache DiffBuilder**|轻量级字段比对|❌|`DiffResult`|简单对象| |**zjsonpatch**|JSON 结构|✅|JSON Patch|API/序列化| 如果你的应用场景需要对比 Java 类的对象数据,`Java Object Differ` 和 `Javers` 是比较合适的选择。如果你需要处理 JSON 数据格式,`zjsonpatch` 可能是更好的方案。 ![CleanShot 2025-02-17 at [email protected]|1000](https://imagehosting4picgo.oss-cn-beijing.aliyuncs.com/imagehosting/fix-dir%2Fmedia%2Fmedia_IZLw1xTjXR%2F2025%2F02%2F17%2F00-27-18-d71fd79004d104519fd306ade3eb3048-CleanShot%202025-02-17%20at%2000.27.05-2x-cd29a0.png) excerpt <!-- more --> ```java package com.qunar.dzs.hotelsearch.polaris.util; import com.google.common.collect.ImmutableSet; import com.qunar.dzs.hotelsearch.polaris.domain.annotation.CompareDesc; import com.qunar.flight.qmonitor.QMonitor; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ClassUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.reflections.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static io.netty.util.internal.StringUtil.LINE_FEED; /** * 对象属性对比工具 **/ public class CompareUtil { private static final Logger logger = LoggerFactory.getLogger(CompareUtil.class); private static final ConcurrentHashMap<Class, Set<Field>> FIELD_CACHE = new ConcurrentHashMap<>(); private static final DateTimeFormatter FORMAT = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); private static final ImmutableSet<Class<? extends Comparable>> COMMON_CLASS_SET = ImmutableSet.of(String.class, Date.class, BigDecimal.class); public static void compareObject(Object oldObject, Object newObject, StringBuffer diffMessage) { //有对象为空,则直接返回 if (Objects.isNull(oldObject) || Objects.isNull(newObject)) { return; } //如果两个对象为不同类型,则直接返回 if (!oldObject.getClass().equals(newObject.getClass())) { return; } Set<Field> allFields = getFields(oldObject.getClass()); for (Field field : allFields) { CompareDesc annotation = field.getAnnotation(CompareDesc.class); if (annotation == null) { continue; } String desc = annotation.desc(); try { PropertyDescriptor pd = new PropertyDescriptor(field.getName(), oldObject.getClass()); Method getMethod = pd.getReadMethod(); Object o1 = getMethod.invoke(oldObject); Object o2 = getMethod.invoke(newObject); if (o1 instanceof Collection && o2 instanceof Collection) { compareCollection(desc, (Collection<?>) o1, (Collection<?>) o2, diffMessage); }else if (o1 instanceof Map && o2 instanceof Map) { compareMap(desc, (Map<?, ?>) o1, (Map<?, ?>) o2, diffMessage); }else { if (!(o1 instanceof Comparable) || !(o2 instanceof Comparable)) { continue; } Comparable oldObjectValue = (Comparable) o1; Comparable newObjectValue = (Comparable) o2; // 如果不是类中定义的普通类型对象, 就继续递归检查其子成员变量 if (!isPrimitive(field)) { compareObject(oldObjectValue, newObjectValue, diffMessage); continue; } if (oldObjectValue != null && newObjectValue == null) { diffMessage.append("删除").append(desc).append(parseDataValue(oldObjectValue)).append(LINE_FEED); } else if (oldObjectValue == null && newObjectValue != null) { diffMessage.append("新增").append(desc).append(parseDataValue(newObjectValue)).append(LINE_FEED); } else if (oldObjectValue != null && newObjectValue.compareTo(oldObjectValue) != 0) { diffMessage.append(desc).append(parseDataValue(oldObjectValue)).append("变为了").append(parseDataValue(newObjectValue)) .append(LINE_FEED); } } } catch (Exception e) { logger.error("变更对比出错", e); QMonitor.recordOne("diffError"); } } } /** * 生成Collection字段的diff信息 */ private static void compareCollection(String collectionType, Collection<?> oldCollection, Collection<?> newCollection, StringBuffer diffMsg) { if (CollectionUtils.isEmpty(oldCollection)) { oldCollection = Collections.emptySet(); } if (CollectionUtils.isEmpty(newCollection)) { newCollection = Collections.emptySet(); } // 使用 HashSet 来处理可能的重复元素问题,并提高性能 Set<?> oldSet = new HashSet<>(oldCollection); Set<?> newSet = new HashSet<>(newCollection); // 找出新增的元素 Set<?> added = new HashSet<>(newSet); added.removeAll(oldSet); // 找出删除的元素 Set<?> removed = new HashSet<>(oldSet); removed.removeAll(newSet); // 追加记录到 diffMsg if (CollectionUtils.isNotEmpty(removed)) { diffMsg.append("被删除的: ").append(collectionType).append(": ").append(JsonUtils.toJson(removed)).append(LINE_FEED); } if (CollectionUtils.isNotEmpty(added)) { diffMsg.append("新增的: ").append(collectionType).append(": ").append(JsonUtils.toJson(added)).append(LINE_FEED); } } private static void compareMap(String mapDesc, Map<?, ?> oldMap, Map<?, ?> newMap, StringBuffer diffMsg) { if (MapUtils.isEmpty(oldMap)) { if (MapUtils.isNotEmpty(newMap)) { diffMsg.append("新增的: ").append(mapDesc).append(": ").append(JsonUtils.toJson(newMap)).append(LINE_FEED); return; } } else if (MapUtils.isEmpty(newMap)) { diffMsg.append("被删除的: ").append(mapDesc).append(": ").append(JsonUtils.toJson(oldMap)).append(LINE_FEED); return; } diffMsg.append(mapDesc).append(parseDataValue(oldMap)).append("变为了").append(parseDataValue(newMap)) .append(LINE_FEED); } private static String parseDataValue(Object data) { if (data instanceof Date) { return new DateTime(data).toString(FORMAT); } else { return data.toString(); } } //获取某个类的所有属性,使用了ConcurrentHashMap做缓存,提升了系统性能 private static Set<Field> getFields(Class clazz) { Set<Field> fields = FIELD_CACHE.get(clazz); if (CollectionUtils.isEmpty(fields)) { Set<Field> allFields = ReflectionUtils.getAllFields(clazz); FIELD_CACHE.put(clazz, allFields); return allFields; } return fields; } // 判断某个对象是否是普通类型或者String,Date,BigDecimal这种可以打印的类型 private static boolean isPrimitive(Field field) { Class<?> type = field.getType(); if (ClassUtils.isPrimitiveOrWrapper(type)) { return true; } for (Class clazz : COMMON_CLASS_SET) { if (clazz.equals(type)) { return true; } } return false; } } ``` `CompareUtil` 类的实现清晰且结构良好,用于比较两个对象的状态并以结构化格式报告差异。然而,还有几个潜在的改进和优化的地方: ## 1. **优化反射的使用** - **缓存改进**:虽然已经使用 `FIELD_CACHE` 缓存类的字段,你还可以考虑缓存每个字段的 `PropertyDescriptor` 实例,以避免在每次调用 `compareObject` 时重新构造它们。每次比较时为每个字段创建 `PropertyDescriptor` 可能相当耗费资源。 - **字段访问性**:如果安全约束允许,你可以设置 `field.setAccessible(true)` 来加速反射访问。这应谨慎使用,并且只在安全策略允许的环境中使用。 ## 2. **错误处理** - **异常处理**:当前你在发生异常时记录错误并记录监控事件。也许还可以更优雅地处理这些错误,比如通过返回包含任何失败信息的结果或状态对象。 - **日志和监控**:考虑添加更详细的日志或调试信息,特别是在关键决策点(如类型比较和递归调用)周围。 ## 3. **类型比较** - **原始类型处理**:`isPrimitive` 方法检查是否属于预定义的简单或 " 原始 " 类型集合。可以考虑扩展此集合以处理更多类型,或通过外部配置这些类型集合。 - **Comparable 处理**:你检查对象是否为 `Comparable` 的实例,但有些对象可能有自然排序,这不是通过 `Comparable` 捕获的。可以通过处理特定情况或使用比较器来增强这一点。 ## 4. **代码清晰度和可维护性** - **魔术字符串和常量**:使用常量替代重复字符串,如 `CompareUtil`0、`CompareUtil`1 和 `CompareUtil`2,以避免打字错误并简化未来的修改。 - **方法分解**:`CompareUtil`3 方法功能较多。考虑将其拆分为更小、更专注的方法,以提高可读性和可维护性。 ## 5. **性能考虑** - **集合比较**:比较集合时,将它们转换为 `CompareUtil`4 可能对大型集合来说效率不高。此外,这种方法会丢失元素的顺序。根据业务需求,考虑是否需要维护元素顺序或以不同方式处理重复项。 - **避免不必要的包装**:在集合和映射比较方法中,有包装到新的 `CompareUtil`5 或类似结构的操作,这可以优化或避免,如果底层集合已经是集合或如果这种包装不是必需的。