6 Star 37 Fork 7

smartboot/obj-compare

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
yamikaze qinluo: d4fba4b 9个月前
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

工具介绍

obj-compare是一款基于Java反射的轻量级对象比较工具。不仅支持常见的POJO对象、集合对象、数组类型比较功能,也支持高度自定义比较定制等功能。

适用场景

  • 单测结果对比以及单测mock框架中参数匹配
  • 业务领域中快照对比记录
  • 测试领域数据对比
  • 文本/json内容对比
  • 一些需要自定义比较的场景

功能列表

  • 普通POJO、Boolean、Date、数组类型比较
  • 容器类List、Set、Map及其子类比较
  • 支持自定义类型比较
  • 支持自定义属性过滤
  • 循环依赖检查、宽松模式等特性

5分钟快速开始

引入Maven依赖

<dependency>
    <groupId>io.github.smartboot.compare</groupId>
    <artifactId>obj-compare</artifactId>
    <version>1.1.3</version>
</dependency>

示例代码

@Test
public void testCompareSimple() {
    User expect = new User();
    expect.setId("1");
    expect.setPassword("111212");
    expect.setUsername("Tom");
    expect.setSex("male");
    
    User target = new User();
    target.setId("2");
    target.setPassword("1112112");
    target.setUsername("Jerry");
    target.setSex("male");
    
    CompareResult result = CompareHelper.compare(expect, target);
    System.out.println(result);
    Assert.assertFalse(result.isSame());
}

结果输出

================   ProcessId : 71ea6da9-977a-4777-a0cc-b362258d4284   ===============
  ● Result      : false
  ● Options     : 
  ● Differences : 3
  ● Recycle     : 0
  ● Escaped     : 128
  ● MaxDepth    : 1


  - difference details :
	1. Based, ROOT.id, 期望值 [1], 实际值 [2]
	2. Based, ROOT.username, 期望值 [Tom], 实际值 [Jerry]
	3. Based, ROOT.password, 期望值 [111212], 实际值 [1112112]
================   ProcessId : 71ea6da9-977a-4777-a0cc-b362258d4284   ===============

Collections/Map/Array Builtin Rules

List/Array Rules

先比较size,size相等再按照index逐一进行对比

Map/Set rules

  • Set 先比较size,size相等遍历期望Set,进行key的有无比较
  • Map 先比较size,size相等遍历期望Map,同时比较key对应的value是否相等

Features

自定义属性过滤

比较过程中,如果需要跳过部分属性的比较,例如时间、随机字符串等字段比较。有以下2种方式供您选择:

  • 使用自定义字段过滤器
  • 使用自定义字段忽略器

字段忽略器最终会被转化为字段过滤器执行,所以您不需要担心2者有太大差异。

自定义字段过滤器

  • 注册全局自定义过滤器
// 过滤对象Field上未标注@Snapshoted的字段
FieldFilters.registerGlobal((f, context) -> f.getType() == Kind.FIELD && f.getField().getAnnotation(Snapshoted.class) == null);
  • 当次比较自定义过滤器
CompareHelper.compare(expect, actual, 0, null, fieldFilter1,fieldFilter2);

自定义字段忽略器

  • 单个字段过滤器
List<IgnoreField> ignoreFields = new ArrayList<>();
// 忽略类型为String.class 字段名为id的 属性比较
ignoreFields.add(new IgnoreField("id", String.class));
CompareHelper.compare(expect, actual, ignoreFields);
  • Pattern字段过滤器
List<IgnoreField> ignoreFields = new ArrayList<>();
// 忽略任何以word结尾的属性,类型为任意类型
IgnorePatternField ignorePatternField = new IgnorePatternField("[\\s\\d]*word");
ignoreFields.add(ignorePatternField);
CompareHelper.compare(expect, actual, ignoreFields);

结果输出

================   ProcessId : a96cbaa7-d4e5-42dc-be52-5765df1487d6   ===============
  ● Result      : true
  ● Options     : 
  ● Differences : 0
  ● Recycle     : 0
  ● Escaped     : 98
  ● MaxDepth    : 0


  skipped fields :
	1:ROOT.id
	2:ROOT.password
================   ProcessId : a96cbaa7-d4e5-42dc-be52-5765df1487d6   ===============

自定义类型/属性比较

如果需要对对象的某些字段进行自定义比较,例如某字段具有特殊的格式,需要展开进行对比,这种场景有2种方式可以实现。

  • 注册全局类型比较器, 如果类型重复,会覆盖默认已有的比较器
ComparatorRegister.register(String.class, new CustomizedStringComparator());
  • 注册Name-Type类型比较器(推荐)
// 匹配名为attribute,值类型为String的字段项进行比较
ComparatorRegister.register(NameType.of("attribute", String.class), AttributesComparator.getInstance());

// 匹配名为c或者d,值类型为Integer的字段项进行比较
ComparatorRegister.register(NamesType.of(Integer.class, "c", "d"), new AbstractComparator<Integer>() {
    @Override
    public Difference compare(Integer expect, Integer actual, ComparatorContext<Integer> context) {
        return Difference.SAME;
        }
    });

自定义类型/属性比较不仅适用于POJO对象,也适用于Map中某个key的比较。

多线程自定义比较器隔离

如果多线程下比较的情况,针对以下case

  • 线程A,针对User的某个String属性需要进行自定义比较1
  • 线程B,针对User的某个String属性需要进行自定义比较2
  • 线程C,针对String类型需要做特殊比较处理3

使用常规的ComparatorRegister无法满足要求,它们之前注册的会相互进行影响,根据此情况,提供了ThreadLocalComparatorRegister来支持。

  • 使用ComparatorRegister注册的比较器仍然会全局生效,覆盖了自带的比较器在比较完后也不会自动重置,需要手动处理
  • ThreadLocalComparatorRegisterComparatorRegister同理,但作用范围在当前线程内
  • 使用ThreadLocalComparatorRegister注册的比较器不会在子线程中派生出的线程生效(因为非InheritableThreadLocal实现)
  • 使用ThreadLocalComparatorRegister如果需要移除,通过方法removeAll移除所有或者使用removeAll单个移除注册的比较器

循环依赖检测

对于两个对象之间的循环依赖字段(非基本类型及其包装类型),对比过程中将会一一记录

示例代码

对象的User字段相互引用

@Test
public void testRecycle() {
    RecycleUser user = new RecycleUser();
    RecycleUser _user = new RecycleUser();

    user.setName("qinluo");
    user.setPassword("haolo2");
    user.setUser(_user);


    _user.setName("qinluo");
    _user.setPassword("haolo1");
    _user.setUser(user);

    CompareResult result = CompareHelper.compare(user, _user);
    System.out.println(result);
}

结果输出

================   ProcessId : a4b6b15b-a229-4224-9f0d-2ffa145ec673   ===============
  ● Result      : false
  ● Options     : 
  ● Differences : 1
  ● Recycle     : 1
  ● Escaped     : 85
  ● MaxDepth    : 1


  messages :
	1:detect recycle reference in path ROOT.user

  difference details :
	1:CommonDifference@ROOT.password, 期望值为 [haolo2], 实际值为 [haolo1]
================   ProcessId : a4b6b15b-a229-4224-9f0d-2ffa145ec673   ===============

输出格式优化

json输出格式能够更直观清晰地观察结果,但需要自行引入相关json序列化包并完成序列器初始化

  • fastjson2、fastjson、gson 引入依赖即可,默认会自动初始化
  • 其他json框架,需要手动初始化

使用示例

// 初始化gson的json序列化
JsonSerializer.setDefaultInstance(new GsonSerializer());
JsonSerializer.setDefaultInstance(new GsonSerializer());

Map<String, Object> expect = new HashMap<>();
expect.put("a", 1);
expect.put("b", "123456888");
expect.put("c", "hhaks");

Map<String, Object> actual = new HashMap<>();
actual.put("a", 1);
actual.put("b", "1234567890");
actual.put("c", "hhaklsa");
actual.put("d", 5);

CompareResult result = CompareHelper.compare(expect, actual);
// 使用json序列化输出结果
System.out.println(new JsonResultViewer(result));

输出示例

{
  "skippedFields": [],
  "differences": 3,
  "differenceDetails": {
    "ROOT.(key)b": {
      "expect": "123456888",
      "actual": "1234567890",
      "level": 1,
      "type": "Based"
    },
    "ROOT.(key)c": {
      "expect": "hhaks",
      "actual": "hhaklsa",
      "level": 1,
      "type": "Based"
    },
    "ROOT.(key)d": {
      "expect": "null",
      "actual": "5",
      "level": 1,
      "type": "Based"
    }
  },
  "maxDepth": 1,
  "escaped": 64,
  "result": false,
  "options": "",
  "messages": [],
  "id": "ed48edb8-e41b-443a-922f-283838b35679",
  "recycleCnt": 0
}

Configuration接口

1.1.1版本新增Configuration接口,用户可实现接口后,将自定义配置信息放入里面

import org.smartboot.compare.Configuration;

class CustomConfiguration implements Configuration {
    
    // 自定义配置
    
}

FeatureFunction

1.1.1版本为了增加比较过程中的自定义处理,新增FeatureFunction功能。目前1.1.1版本中支持以下几个特性

  • 判断在某个路径下的option是否生效(默认生效)
default Boolean isEffectOption(ComparatorContext<?> ctx, Option option) {
    return true;
}
  • string比较中将string转换为其他对象进行比较,例如json
default Object convertString2Object(ComparatorContext<?> ctx, String value) {
    return value;
}
  • 数组比较前的排序
default void sort(ComparatorContext<?> ctx, Object expectArray, Object actualArray, int type) {

}

具体使用可以参照以下示例


ComparatorContext<Object> ctx = new ComparatorContext<>(options);
ctx.setExpect(expect);
ctx.setActual(actual);
ctx.setFeatureFunction(new FeatureFunction() {
    
     private boolean supportConvert2Json(Path path) {
         return true;
     } 
    
     public Object convertString2Object(ComparatorContext<?> ctx, String value) {
         if (!supportConvert2Json(ctx.getPath())) {
             return null;
        }
         
         return JSON.toJSONObject(value);
     }   


});

模型解析

比较结果解析

属性 类型 释义
differences List<Difference> 差异项列表,为空说明比较结果一致
skippedFields List<String> 比较过程中跳过比较的属性列表
messages List<String> 比较过程中添加的一些信息
recycleCnt int 循环依赖次数
options long 比较选项bits
escaped long 比较耗时,单位ms
id String 比较id,默认为UUID
maxDepth int 比较层级的最大深度

Difference解析

默认所有自带的Difference都继承AbstractDifference, 自带路径path

Type 类型 释义
Based BaseDifference 基本差异对象,包括expect和actual
Size SizeDifference Array/List/Set/Map 期望大小不一致,expect和actual为对应size
NullOfOne NullOfOneObject expect和actual其中一个对象为空
ComplementSet ComplementSetDifference 用于集合/数组之间的差异,会记录期望集合与实际集合各自的补集
TypeUnmatched TypeDifference expect和actual的类型不一致
Error DifferenceError 调用POJO自带的equals方法出现异常
CustomizedAttribute AttributeCompareDifference 默认提供的attribute比较不一致结果

Options

在正常比较之于,obj-compare提供了一些可选项,每个可选项都会对对比过程产生影响。您可以使用以下方式进行设置一个或多个Option

CompareHelper.compare(expect, actual, Option.mix(LOOSE_MODE, DISABLE_EQUALS));

宽松模式LOOSE_MODE

正常模式下,会严格对比对象的class以及值是否一致。而在宽松模式下,将会放宽一些比对处理:

  • 如果是List、Map、Set类型,只比较数据本身的差异,size == 0与null视为相等
  • String类型,""与null视为相等
  • Boolean类型,值null视为false

示例代码

@Test
public void testLooseMode() {
    List<String> names = new ArrayList<>(100);
    names.add("tom");
    names.add("Jerry");
    names.add(null);

    List<String> linkedNames = new LinkedList<>();
    linkedNames.add("tom");
    linkedNames.add("Jerry");
    linkedNames.add("");

    // default not strict mode
    CompareResult result = CompareHelper.compare(names, linkedNames, Option.mix(Option.LOOSE_MODE));
    System.out.println(result);

    System.out.println("===================================\n");

    result = CompareHelper.compare(names, linkedNames);
    System.out.println(result);
}

结果输出 LOOSE_MODE

================   ProcessId : e31d9b79-d4d5-4e97-b520-e21e662671c7   ===============
  ● Result      : true
  ● Options     : LOOSE_MODE
  ● Differences : 0
  ● Recycle     : 0
  ● Escaped     : 15
  ● MaxDepth    : 1

================   ProcessId : e31d9b79-d4d5-4e97-b520-e21e662671c7   ===============

结果输出 无LOOSE_MODE

================   ProcessId : 5856c594-97a9-4fc8-9b55-63373af0abdf   ===============
  ● Result      : false
  ● Options     : 
  ● Differences : 1
  ● Recycle     : 0
  ● Escaped     : 5
  ● MaxDepth    : 0


  difference details :
	1:typeDifference ,比较路径 ROOT, 期望类型为 [java.util.ArrayList, [tom, Jerry, null]], 实际类型为 [java.util.LinkedList, [tom, Jerry, ]]
================   ProcessId : 5856c594-97a9-4fc8-9b55-63373af0abdf   ===============

禁用自定义equals方法 DISABLE_EQUALS

默认情况下,如果比较的POJO对象定义了equals方法,默认会调用该方法进行比较。 但一些情况下,例如equals方法由lombok生成,即使2个对象各个属性一致,但也可能会不相等。 此时可以使用以下示例禁用equals比较。

测试POJO对象

只要idCard属性相等就认为2个对象相等。

private class EqualsUser {
    private String name;
    private String sex;
    private String idCard;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        EqualsUser that = (EqualsUser) o;
        // idCard相等就相等
        return Objects.equals(idCard, that.idCard);
    }
}

示例代码

@Test
public void testCustomizedEquals() {
    EqualsUser expect = new EqualsUser();
    expect.name = "Tom";
    expect.sex = "man";
    expect.idCard = "123456";

    EqualsUser actual = new EqualsUser();
    actual.name = "Jerry";
    actual.sex = "woman";
    actual.idCard = "123456";

    // Use equals
    CompareResult result = CompareHelper.compare(expect, actual);
    System.out.println(result);

    Assert.assertTrue(result.isSame());

    // Disable equals method.
    result = CompareHelper.compare(expect, actual, Option.mix(Option.DISABLE_EQUALS));
    System.out.println(result);

    Assert.assertFalse(result.isSame());
    Assert.assertEquals(result.getDifferences().size(), 2);
}

结果输出 未禁用equals

================   ProcessId : 0a1a6fb4-135e-4c8b-a612-5d5060242240   ===============
  ● Result      : true
  ● Options     : 
  ● Differences : 0
  ● Recycle     : 0
  ● Escaped     : 14
  ● MaxDepth    : 0

================   ProcessId : 0a1a6fb4-135e-4c8b-a612-5d5060242240   ===============

结果输出 禁用equals

================   ProcessId : b0e2506b-63e0-476d-a607-8a421938c456   ===============
  ● Result      : false
  ● Options     : DISABLE_EQUALS
  ● Differences : 2
  ● Recycle     : 0
  ● Escaped     : 140
  ● MaxDepth    : 1


  difference details :
	1:CommonDifference@ROOT.name, 期望值为 [Tom], 实际值为 [Jerry]
	2:CommonDifference@ROOT.sex, 期望值为 [man], 实际值为 [woman]
================   ProcessId : b0e2506b-63e0-476d-a607-8a421938c456   ===============

立即中断 IMMEDIATELY_INTERRUPT

默认情况下,会逐一比对两个对象之间的所有差异,例如一个POJO对象/Map有10项属性,每项属性都会进行对比,如果不关心对象之间的所有差异细节, 只想知道是否有差异,那么推荐您使用IMMEDIATELY_INTERRUPT

示例代码

@Test
public void testCompareMapWithImmediatelyInterrupt() {
    Map<String, Integer> expect = new HashMap<>();
    expect.put("a", 1);
    expect.put("b", 2);
    expect.put("c", 3);
    expect.put("d", 3);

    Map<String, Integer> actual = new HashMap<>();
    actual.put("a", 1);
    actual.put("b", 2);
    actual.put("c", 4);
    actual.put("d", 5);

    CompareResult result = CompareHelper.compare(expect, actual);
    System.out.println(result);
    Assert.assertFalse(result.isSame());
    Assert.assertEquals(result.getDifferences().size(), 2);

    result = CompareHelper.compare(expect, actual, Option.mix(Option.IMMEDIATELY_INTERRUPT));
    System.out.println(result);
    Assert.assertFalse(result.isSame());
    Assert.assertEquals(result.getDifferences().size(), 1);
}

结果输出 无IMMEDIATELY_INTERRUPT

================   ProcessId : b8b68db9-ad42-4868-91fa-3e2a2d80b33a   ===============
  ● Result      : false
  ● Options     : 
  ● Differences : 2
  ● Recycle     : 0
  ● Escaped     : 16
  ● MaxDepth    : 1


  difference details :
	1:CommonDifference@ROOT.(key)c, 期望值为 [3], 实际值为 [4]
	2:CommonDifference@ROOT.(key)d, 期望值为 [3], 实际值为 [5]
================   ProcessId : b8b68db9-ad42-4868-91fa-3e2a2d80b33a   ===============

结果输出 指定IMMEDIATELY_INTERRUPT

================   ProcessId : 35329eb0-2acd-4fc4-8938-8f719f6b6fec   ===============
  ● Result      : false
  ● Options     : IMMEDIATELY_INTERRUPT
  ● Differences : 1
  ● Recycle     : 0
  ● Escaped     : 1
  ● MaxDepth    : 1


  difference details :
	1:CommonDifference@ROOT.(key)c, 期望值为 [3], 实际值为 [4]
================   ProcessId : 35329eb0-2acd-4fc4-8938-8f719f6b6fec   ===============

优化数组比较结果 BEAUTIFUL_ARRAY_RESULT

默认情况下,数组的比较输出如下: 按照index逐一进行比对并输出差异项

================   ProcessId : 76deec02-8e8b-4b3b-9311-31a02b723f34   ===============
  ● Result      : false
  ● Options     : 
  ● Differences : 5
  ● Recycle     : 0
  ● Escaped     : 48
  ● MaxDepth    : 0

  difference details :
	1:CommonDifference@ROOT.(index)0, 期望值为 [0], 实际值为 [9]
	2:CommonDifference@ROOT.(index)1, 期望值为 [1], 实际值为 [8]
	3:CommonDifference@ROOT.(index)2, 期望值为 [2], 实际值为 [7]
	4:CommonDifference@ROOT.(index)3, 期望值为 [3], 实际值为 [6]
	5:CommonDifference@ROOT.(index)4, 期望值为 [4], 实际值为 [5]
================   ProcessId : 76deec02-8e8b-4b3b-9311-31a02b723f34   ===============

如果想要简化下结果输出,可以在比较时,指定Option: BEAUTIFUL_ARRAY_RESULT

================   ProcessId : a3c6467c-e6eb-40fc-b8af-1e6450a84f7c   ===============
  ● Result      : false
  ● Options     : BEAUTIFUL_ARRAY_RESULT
  ● Differences : 1
  ● Recycle     : 0
  ● Escaped     : 24
  ● MaxDepth    : 0


  difference details :
	1:CommonDifference@ROOT, 期望值为 [[0, 1, 2, 3, 4]], 实际值为 [[9, 8, 7, 6, 5]]
================   ProcessId : a3c6467c-e6eb-40fc-b8af-1e6450a84f7c   ===============

如果指定了Option: BEAUTIFUL_ARRAY_RESULT, 数组的比较也相当于指定了Option: IMMEDIATELY_INTERRUPT

转换List/Array为Set进行比较 TRANS_AS_SET

List/Array比较策略为先判断size,然后再按照index逐一进行对比,如果期望转换为Set进行比较:即两个数组中包含的元素一样,但对应的位置不同,也认为相同

@Test
public void testCompareInt4() {
    int[] expect = new int[12];
    int[] actual = new int[10];
    for (int i = 0; i < 10; i++) {
        expect[i] = i;
        actual[i] = 10 - i - 1;
    }

    CompareResult result = CompareHelper.compare(expect, actual, Option.mix(Option.TRANS_AS_SET));
    System.out.println(result);
}
  • 结果输出
================   ProcessId : ce4206a7-6f1e-450d-97df-440d79ea457f   ===============
  ● Result      : true
  ● Options     : TRANS_AS_SET
  ● Differences : 0
  ● Recycle     : 0
  ● Escaped     : 45
  ● MaxDepth    : 0

================   ProcessId : ce4206a7-6f1e-450d-97df-440d79ea457f   ===============

USE_EXPECT_TYPE_IN_MAP

为了解决Map中,相同key对应的值属性不同的类型表现形式的时比较,例如如下case

{
  "age": 1
}

不同形式比较

{
  "age": "1"
}

正常情况下,比较将会返回不一致。但其实通过人工比较的方式可以知道它们其实是一致的,所以可以这样进行比较


Map<String, Object> v1 = new HashMap<>();
Map<String, Object> v2 = new HashMap<>();

ComparatorRegister.register(NameType.of(Object.class, "age"), new AbstractComparator<Object> {
    
    @Override
    public Difference compare(Object v1, Object v2, ComparatorContext<Object> ctx) {
        String v1s = String.valueOf(v1);
        String v2s = String.valueOf(v2);
        if (Objects.equals(v1s, v2s)) {
            return null;
        }
        return new BaseDifference(ctx.getPath(), v1, v2);
        
    }
});

CompareResult result = CompareHelper.compare(v1, v2, Option.mix(Option.USE_EXPECT_TYPE_IN_MAP));

测试报告

单测覆盖率

img.png

其他功能

欢迎提Issue

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

一款基于Java反射的对象比较工具,适用于单测参数匹配、数据结果对比,快照落存等场景 展开 收起
README
Apache-2.0
取消

发行版 (8)

全部

贡献者

全部

语言

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/smartboot/obj-compare.git
git@gitee.com:smartboot/obj-compare.git
smartboot
obj-compare
obj-compare
master

搜索帮助