超越 JSON: Spearal 序列化协议简介 已翻译 100%

孙国强JAVA 投递于 2014/10/07 09:00 (共 40 段, 翻译完成于 12-03)
阅读 6312
收藏 15
2
加载中

Spearal 是一个新的开源的序列化协议,这个协议旨在初步替换JSON 将HTML和移动应用连接到Java的后端.

Spearal的主要目的是提供一个序列协议,这个协议即使是在端点间传输的复杂的数据结构中也可以简单工作: 我们很快就能看到JSON的一些局限将会害了开发者, 这些局限是不会发生在一个好的通用的序列化格式中的.

抛开这个主要目的, Spearal还提供了在标准JSON中没有的高级功能, 如局部对象序列化,内建的对JPA的非初始化关联, 不同型号的协调, 对象特性过滤等. 虽然还在初期发展阶段, 但是Spearal再将HTML应用连接到Java后端上已经很有用了. Android已经做好了准备IOS也快要跟上了.

内容列表:

  1. 背景

  2. JSON怎么了?

  3. 一个好的序列化协议是怎样的?

  4. Spearal序列化协议时怎么样的?

  5. 说够了, 来看看Spearal干了些什么!

  6. 运行Spearal的AngularJS / JAX-RS / JPA 简单应用

  7. 服务器端使用JSON失败

  8. 处理过时的客户端应用

  9. 结论和未来的工作

对于那些心急的想要看到Spearal如何工作的人, 你可以跳过本文的前四页,直接跳到and jump 够了, 来看看Spearal干了些什么那一页去!.

小猪猪0406
翻译于 2014/10/10 13:04
1

背景

随着Web的不断发展,以及异步数据请求在移动应用上的大规模应用,序列化已经变成了一个重大问题。在古老的纯html时代,像是序列化格式,DTO,数据循环引用(data graph circularity),引用(references)以及java(或者其它后端技术)和javascript之间的数据交互之类的东西根本不用去考虑

早在2000年,XMLHttpRequest就被创造出来,使得独立于html页面的对象序列化成为可能。1999年,微软首先在Internet Explorer5.0中引入了Microsoft.XmlHttp对象。不久之后,Mozilla和苹果实现了一个兼容的XMLHttpRequest 对象,这在2006变成了W3C的推荐实现。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/10 15:45
2

顾名思义,XMLHttpRequest 最初的设计是使用 XML 作为序列化格式。Douglas Crockford 在 2002 年发布了 JSON,从此之后,JSON 迅速变成了XML的替代品,原因就是它比xml更轻(这里有些关于JSON vs. XML 的很有趣的讨论,这里)。

JSON 现在已经变成了应用之间交换数据的事实标准,而不仅限于基于HMLT/JavaScript的应用。当然也有一些其他的替代品,比如谷歌的 Protocol Buffers,但是这些替代品没有一个能像JSON这样流行。

现在XMLHttpRequest规范提供了一种标准方法来序列化和处理二进制数据——ArrayBuffer(精确来说是 W3C Working Draft of the 16 August 2011),是时候反思JSON的局限性以及如何实现一个更好的协议了。这就是这篇文章的目的以及 Spearal 项目的目标。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/10 14:18
1

纳尼?JSON 怎么了?

我们非常清楚: JSON 拥有非常好的可读性、易上手, 易懂的序列化格式,而且对单一的对象的表达非常清晰。但是他有一个不可逾越的限制(最新JSON标准):当你在操作(序列化)任何自循环的对象的时候他会失败:

var selfish = {};
selfish.friend = selfish;
JSON.stringify(selfish);

如果你在浏览器上运行了上面代码,以肯定会出现以下错误:

Uncaught TypeError: Converting circular structure to JSON

当然这是一个特殊的例子, 这是不寻常的设计数据模型与循环引用,然而在编程语言(尤其是javascript)、数据库和JPA引擎它可以很好支持自循环机构。

Leo_Vip
Leo_Vip
翻译于 2014/10/10 12:16
1

另一个限制是标准json格式缺乏对对象指针的支持。如果你序列化一个包含两个对象的数组,两个对象都指向一个共同的对象,那么这个共同的对象将会被序列化两次。这不仅仅是浪费传输带宽的问题(如果用对象指针而不是两次序列化同一个对象,那么生成的结果要小一些),这也会破坏你数据图(data graph)的一致性(consistency),举例如下:

var child = { age: 1 };
var parents = [{ child: child }, { child: child }];

parents[0].child.age = 2;
console.log("Should be 2: " + parents[1].child.age);

parents = JSON.parse(JSON.stringify(parents));

parents[0].child.age = 3;
console.log("Should be 3: " + parents[1].child.age);

上面代码的输出如下:

Should be 2: 2
Should be 3: 2

子对象在序列化和反序列化之后已经被复制了,两个父对象不再指向同一实例。JSON中没有标准方法来处理引用(同样不能处理循环引用cyclic graph),这种特性不在规范里面。关于这点可以去看下ECMA规范(”JSON不支持循环引用,至少不直接支持“)以及非标准的插件cycle.js

孙国强JAVA
孙国强JAVA
翻译于 2014/10/10 13:43
1

另一个JSON的问题是它不保存所序列化对象的类名。让我们来执行下面这一段代码:

function BourneIdentity(name) {
    this.name = name;
}

var obj = new BourneIdentity("Jason");
console.log("Should be a BourneIdentity: " + (obj instanceof BourneIdentity));

obj = JSON.parse(JSON.stringify(obj));

console.log("Should be a BourneIdentity: " + (obj instanceof BourneIdentity));

下面是输出结果:

Should be a BourneIdentity: true
Should be a BourneIdentity: false

JavaScript的Date对象也有这个问题:

var date = new Date()
console.log("Should be a Date: " + (date instanceof Date));
date = JSON.parse(JSON.stringify(date));
console.log("Should be a Date: " + (date instanceof Date));

下面是输出结果:

Should be a Date: true
Should be a Date: false

当然,你可以找到很多非标准的方法来解决这类问题(比如这里)。但是使用这种非标准的替换恢复的trick越多,你JSON编码和解码的效率就会越低。

目前为止,我只涉及到从标准JSON格式中继承来的限制。而在真正的服务器和客户端数据交互中,事情只会变得更糟(比如java服务器和HTML app)。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/10 13:55
1

Java 有很多 JSON 库,比如 Jackson 和 Gson。这些类库极大的方便了从JSON数据中解析出JAVA对象。JAX-RS也有对JSON格式的原生支持(通常是Jackson)。在处理非常简单的数据模型(model)的时候,这些类库都没有什么问题。但是对于复杂的数据模型,它们会出现很多问题。在后面的 JavaEE/AngularJS 例子中将会进一步介绍这一点。

总的来说,根据我的经验和很多其它开发者的反馈,在实际开发中,序列化已经变成一个很棘手的问题。尽管几乎每个人都在使用JSON(因为它是事实上的数据传输标准),序列化依然在用各种恼人无趣的问题毒害我们。我绝不是想说JSON有多烂或者说它在实际开发中毫无用处。但是人们依然在花费大量的时间和精力来解决各种各样的序列化问题,而对于一个好的序列化格式来说,这些问题根本就不应该出现。去看吧,在DZone上有差不多1000篇关于Jackson的文章。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/10 17:10
1

一个好的序列化协议应该是什么样子的?

下面是一个理想的序列化协议的关键特性列表(排名不分先后)

1. 压缩率高,速度快并且可读:

通过互联网进行传输数据(尤其是移动互联网),性能表现,包括输出大小和执行速度,一直是一个关键指标。序列化数据,即使被序列化成了二进制数据,也应该可以很容易的通过一些工具被转换成人类可读的文本

2. 处理任意复杂的数据结构(graph of objects):

序列化格式不应该强迫我们降低数据结构的复杂度。任何数据结构都应该是可以序列化的,不管是否包含循环引用(cyclic references)。并且共享的实例在序列化反序列化之后仍然应该是共享的,而不是被复制成多份。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/11 09:04
1

3. 能够只序列化需要的属性而不是整个对象

如果一个应用只需要一个复杂对象的一部分属性,应该有一种方式只请求这一部分属性而不是整个对象。与之相反,如果复杂对象中只有一小部分属性被更新,也应该有一种方式可以只向服务器发送这一小部分属性。

4. 在没有DTOs(数据传输对象)的情况下,正确的处理过期数据模型:

当服务器端的数据模型追加新的属性的时候,有可能有个别客户端没有被同步更新。这些过期的客户端使用之前版本的数据模型,但也可应该可以通过忽略服务器发过来数据的未知属性来继续运行。另一方面,服务器从过期客户端拿到的是不完整数据,但是不应该因为新属性的缺失而将既存的属性置空。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/11 09:22
1

5. 保存非匿名类实例的类名:

如果类X的实例被序列化了,哪么反序列化之后应该还是X的实例,除非反序列化的上下文没有定义类X。这个需求意味着需要在序列化数据中加入对象的类名。

6. 如果一个类不应该被序列化,那么应该可以阻止它被序列化:

必须要有一种方式可以限制可序列化对象的范围。比如,阻止所有HTTP session对象的序列化。

7. 能够处理JPA未被初始化的属性:

序列化协议应该提供一个拓展用来正确处理未初始化的JPA属性,以防抛出LazyInitializationException以及不想要集合(collection 译者注:这里应该是指ORM类中lazy-loading的外键属性,外键属性一般都是数组,对应着一对多的关系)的初始化和序列化。更重要的是,序列化协议一定要区分出null属性和未初始化的lazy-loading属性。不然的话,有可能导致lazy-loading属性在数据库更新时被置空。

孙国强JAVA
孙国强JAVA
翻译于 2014/10/11 19:23
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(4)

ToSun
ToSun
上面的话都看不明白
句龙胤
句龙胤

引用来自“maverickpuss”的评论

技术每天都在进步, 然而最终盛行于世的不是那些最完美的, 而是最通用,成本可控, 老少咸宜的, 这也是为何windows在大众领域远胜于linux的原因.
Linux可不完美,其缺陷和缺点远比windows严重,根源就是错误的以unix为方向,承袭了很多unix的错误思想。
狗头666
狗头666

引用来自“maverickpuss”的评论

技术每天都在进步, 然而最终盛行于世的不是那些最完美的, 而是最通用,成本可控, 老少咸宜的, 这也是为何windows在大众领域远胜于linux的原因.
+1明白人不多
maverickpuss
maverickpuss
技术每天都在进步, 然而最终盛行于世的不是那些最完美的, 而是最通用,成本可控, 老少咸宜的, 这也是为何windows在大众领域远胜于linux的原因.
返回顶部
顶部