惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
博客园_首页
Vercel News
Vercel News
WordPress大学
WordPress大学
美团技术团队
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 【当耐特】
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
腾讯CDC
M
MIT News - Artificial intelligence
爱范儿
爱范儿
Recent Announcements
Recent Announcements
雷峰网
雷峰网
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
Jina AI
Jina AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
Help Net Security
Help Net Security
N
News and Events Feed by Topic
博客园 - Franky
P
Proofpoint News Feed
L
LINUX DO - 热门话题
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
D
Docker
Google DeepMind News
Google DeepMind News
有赞技术团队
有赞技术团队
IT之家
IT之家
Security Latest
Security Latest
L
LangChain Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks

懒程序员改变世界

AI时代的站会:从同步到异步 真实流量,本地回放:一种轻量的性能测试思路 AI编程成熟度模型:从3倍到10倍的路径 用AWS创建工作流 未来的软件开发什么样 Java 11时代,这些编程习惯可以改一改了 手稿秒变html的Sketch 2 Code 组合型软件架构 逆向工作法 挖掘IntelliJ IDEA的调试功能 软件开发有没有高下 挖掘IntelliJ IDEA的实用功能 敲最少的键,编最多的码 个人的技术债 《Java函数式编程》出版啦 懒程序员笔记 SSH妙用 用Tampermonkey实现自动化网页操作 代码还是数据
用JaVers比较对象
Qinghua Gao · 2018-07-14 · via 懒程序员改变世界

JaVers是一个轻量级的对象比较/审计框架,非常易于使用。当前的JaVers版本3是用Java 8编写的,只能运行在JRE 8或以上版本。2.9.2是最后一个和Java 7兼容的版本。源代码在此

简介

如果你需要实时比较生产环境的处理结果和备份环境的处理结果,或是在新系统中重放生产环境的请求,或者像代码一样对对象进行版本管理,那么JaVers就可以成为你的好朋友。它不仅可以比较对象,也可以将比较结果存储在数据库中以便审计。审计是这样的一种需求:

作为用户,我希望知道谁改变了状态,
是什么时候改变的,以及原先的状态是什么。

本文仅关注于比较部分,对审计部分就不具体展开了。

快速一览

新建Maven工程,往pom.xml中增加dependency,最后的pom.xml看起来就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

4.0.0

org.ggg.javers
helloJaVers
1.0-SNAPSHOT

helloJaVers



org.javers
javers-core
3.9.6


com.google.guava
guava
25.0-jre


org.projectlombok
lombok
1.16.20
provided



假设我们有一个名为Staff的POJO如下:

1
2
3
4
5
6
7
8
9
10
11
12
@ToString
@Builder
@Data
public class Staff {
private String name;
private int age;
private Double height;
private BigDecimal salary;
private Staff manager;
private List hobbies;
private Map phones;
}

main函数中如下编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Staff tommy = Staff.builder()
.name("Tommy")
.age(18)
.height(180d)
.salary(new BigDecimal("10000"))
.hobbies(Lists.newArrayList("film", "game"))
.phones(ImmutableMap.of("home", "1234", "office", "4321"))
.manager(Staff.builder().name("ok").build())
.build();
Staff ggg = Staff.builder()
.name("ggg")
.age(17)
.height(180.000000001d)
.hobbies(Lists.newArrayList("game", "music", "travel"))
.phones(ImmutableMap.of("mobile", "4321", "home", "1235"))
.manager(Staff.builder().name("ok").build())
.build();

Javers javers = JaversBuilder.javers().build();
Diff diff = javers.compare(tommy, ggg);
System.out.println(diff);

即可得到以下输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Diff:
* changes on org.ggg.javers.Staff/ :
- 'age' changed from '18' to '17'
- 'height' changed from '180.0' to '180.000000001'
- 'hobbies' collection changes :
0. 'film' changed to 'game'
1. 'game' changed to 'music'
2. 'travel' added
- 'name' changed from 'Tommy' to 'ggg'
- 'phones' map changes :
'home' -> '1234' changed to '1235'
'mobile' -> '4321' added
'office' -> '4321' removed
- 'salary' changed from '10000' to ''

大部分的代码都是我们创建对象所用的,可见JaVers非常易于使用。

对象类型

根据DDD,JaVers把要比较的对象分为三种类型:实体(Entity)、值对象(Value Object)和值(Value)。每种类型都有不同的比较方法。值最简单,就是看它们是否是Object.equals()的。实体和值对象都是按属性依次比较。它们俩的区别是实体拥有标识(Entity Id),而值对象并没有。标识不同的实体就会被认为是不同的对象,从而不再继续比较其余的字段。从DDD的角度上严格来说值对象不能单独存在,需要依附于实体。好在JaVers并不教条,值对象也可以用来单独比较。上面的代码其实就是把Staff对象当作值对象来比较。如果我们在Staff类中,给name添加一个@Id的注解(所有的注解都在org.javers.core.metamodel.annotation包里),那么比较结果就会不同:

1
2
3
Diff:
* new object: org.ggg.javers.Staff/ggg
* object removed: org.ggg.javers.Staff/Tommy

只有当name属性相同的时候,这两个对象才会被当成同一实体,从而依次比较其余属性。如果没有权限修改实体以增加@Id注解,也可以用这样的方法来注册,效果相同:

1
2
3
Javers javers = JaversBuilder.javers()
.registerEntity(new EntityDefinition(Staff.class, "name"))
.build();

如果registerEntity的属性和@Id注解都存在,那么以registerEntity所注册的属性为准。

JaVers完全兼容Groovy,可以参考其文档来了解用例。

自定义比较方式

忽略属性

从业务上说,有些属性新、旧系统本来就不一样,或者是不关心,这时候我们可以在比较中“忽略”这些属性。如果有权限修改要比较的对象类,可以简单地在属性上面增加一个@DiffIgnores,比较的时候就会将其忽略。@DiffIgnores相当于黑名单,@DiffInclude起到了白名单的效果。如果没有修改类的权限,那么也可以用这样的方法来注册,效果相同:

1
2
3
Javers javers = JaversBuilder.javers()
.registerValueObject(new ValueObjectDefinition(Staff.class, Lists.newArrayList("hobbies", "phones")))
.build();

这里我们注册的是个值对象ValueObject,与上一个例子的实体Entity的区别就是有没有标识属性。

比较算法

一开始细心的读者们就可能注意到了,Lists.newArrayList("film", "game")Lists.newArrayList("game", "music", "travel")的比较结果居然是:

1
2
3
4
- 'hobbies' collection changes :
0. 'film' changed to 'game'
1. 'game' changed to 'music'
2. 'travel' added

这当然也是可以配置的:

1
2
3
Javers javers = JaversBuilder.javers()
.withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE)
.build();

这样的话,结果就变成了:

1
2
3
4
- 'hobbies' collection changes :
2. 'travel' added
1. 'music' added
0. 'film' removed

值得注意的是,这种算法在列表元素过多的时候可能会影响性能。

定制比较

我们可以注册自定义的比较器,例如,如果在业务上认为两个Double类型的1.0000000011相等,这时候我们可以注册一个如下的类:

public class CustomDoubleIgnorePrecisionComparator implements CustomPropertyComparator {

    private static final double DELTA = 0.00001;

    public ValueChange compare(final Double left, final Double right, final GlobalId affectedId, final Property property) {
        final double diff = Math.abs(left - right);
        if (diff 关联字段比较
关联字段就是说,如果几个字段之间有关联,我们就认为它们一样。比如说我们虚构一个需求:对于一个拥有`int x`和`int y`的`Rectangle`类来说,如果`x * y`也就是面积相等,我们就认为它们相等。在这种情况下,JaVers似乎并没有原生提供关联字段比较的办法。有一种办法是新建一个包装类,比如说`RectangleWrapper`,里面有一个`Rectangle rectangle`和一个`int area`字段,分别赋值为要比较的对象和其`x * y`。注册`Javers`的时候,把`Rectangle`类的`x`和`y`忽略即可。如果有更复杂的需求,例如当面积不同时需要报`x`和`y`不同而不是`area`不同,那也可以通过生成多个`Javers`对象,并多次调用来解决。`Javers#compare`方法返回的是一个`Diff`对象,从中可以很方便地查看某些字段是否存在变化。就是要多写点代码咯。