Spearal 是一个新的开源的序列化协议,这个协议旨在初步替换JSON 将HTML和移动应用连接到Java的后端.
Spearal的主要目的是提供一个序列协议,这个协议即使是在端点间传输的复杂的数据结构中也可以简单工作: 我们很快就能看到JSON的一些局限将会害了开发者, 这些局限是不会发生在一个好的通用的序列化格式中的.
抛开这个主要目的, Spearal还提供了在标准JSON中没有的高级功能, 如局部对象序列化,内建的对JPA的非初始化关联, 不同型号的协调, 对象特性过滤等. 虽然还在初期发展阶段, 但是Spearal再将HTML应用连接到Java后端上已经很有用了. Android已经做好了准备IOS也快要跟上了.
内容列表:
背景
JSON怎么了?
一个好的序列化协议是怎样的?
Spearal序列化协议时怎么样的?
说够了, 来看看Spearal干了些什么!
运行Spearal的AngularJS / JAX-RS / JPA 简单应用
服务器端使用JSON失败
处理过时的客户端应用
结论和未来的工作
对于那些心急的想要看到Spearal如何工作的人, 你可以跳过本文的前四页,直接跳到and jump 够了, 来看看Spearal干了些什么那一页去!.
随着Web的不断发展,以及异步数据请求在移动应用上的大规模应用,序列化已经变成了一个重大问题。在古老的纯html时代,像是序列化格式,DTO,数据循环引用(data graph circularity),引用(references)以及java(或者其它后端技术)和javascript之间的数据交互之类的东西根本不用去考虑
早在2000年,XMLHttpRequest就被创造出来,使得独立于html页面的对象序列化成为可能。1999年,微软首先在Internet Explorer5.0中引入了Microsoft.XmlHttp对象。不久之后,Mozilla和苹果实现了一个兼容的XMLHttpRequest 对象,这在2006变成了W3C的推荐实现。
顾名思义,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 项目的目标。
我们非常清楚: JSON 拥有非常好的可读性、易上手, 易懂的序列化格式,而且对单一的对象的表达非常清晰。但是他有一个不可逾越的限制(最新JSON标准):当你在操作(序列化)任何自循环的对象的时候他会失败:
var selfish = {}; selfish.friend = selfish; JSON.stringify(selfish);
如果你在浏览器上运行了上面代码,以肯定会出现以下错误:
Uncaught TypeError: Converting circular structure to JSON
当然这是一个特殊的例子, 这是不寻常的设计数据模型与循环引用,然而在编程语言(尤其是javascript)、数据库和JPA引擎它可以很好支持自循环机构。
另一个限制是标准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。
另一个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 有很多 JSON 库,比如 Jackson 和 Gson。这些类库极大的方便了从JSON数据中解析出JAVA对象。JAX-RS也有对JSON格式的原生支持(通常是Jackson)。在处理非常简单的数据模型(model)的时候,这些类库都没有什么问题。但是对于复杂的数据模型,它们会出现很多问题。在后面的 JavaEE/AngularJS 例子中将会进一步介绍这一点。
总的来说,根据我的经验和很多其它开发者的反馈,在实际开发中,序列化已经变成一个很棘手的问题。尽管几乎每个人都在使用JSON(因为它是事实上的数据传输标准),序列化依然在用各种恼人无趣的问题毒害我们。我绝不是想说JSON有多烂或者说它在实际开发中毫无用处。但是人们依然在花费大量的时间和精力来解决各种各样的序列化问题,而对于一个好的序列化格式来说,这些问题根本就不应该出现。去看吧,在DZone上有差不多1000篇关于Jackson的文章。
下面是一个理想的序列化协议的关键特性列表(排名不分先后)
通过互联网进行传输数据(尤其是移动互联网),性能表现,包括输出大小和执行速度,一直是一个关键指标。序列化数据,即使被序列化成了二进制数据,也应该可以很容易的通过一些工具被转换成人类可读的文本
序列化格式不应该强迫我们降低数据结构的复杂度。任何数据结构都应该是可以序列化的,不管是否包含循环引用(cyclic references)。并且共享的实例在序列化反序列化之后仍然应该是共享的,而不是被复制成多份。
如果一个应用只需要一个复杂对象的一部分属性,应该有一种方式只请求这一部分属性而不是整个对象。与之相反,如果复杂对象中只有一小部分属性被更新,也应该有一种方式可以只向服务器发送这一小部分属性。
当服务器端的数据模型追加新的属性的时候,有可能有个别客户端没有被同步更新。这些过期的客户端使用之前版本的数据模型,但也可应该可以通过忽略服务器发过来数据的未知属性来继续运行。另一方面,服务器从过期客户端拿到的是不完整数据,但是不应该因为新属性的缺失而将既存的属性置空。
如果类X的实例被序列化了,哪么反序列化之后应该还是X的实例,除非反序列化的上下文没有定义类X。这个需求意味着需要在序列化数据中加入对象的类名。
必须要有一种方式可以限制可序列化对象的范围。比如,阻止所有HTTP session对象的序列化。
序列化协议应该提供一个拓展用来正确处理未初始化的JPA属性,以防抛出LazyInitializationException以及不想要集合(collection 译者注:这里应该是指ORM类中lazy-loading的外键属性,外键属性一般都是数组,对应着一对多的关系)的初始化和序列化。更重要的是,序列化协议一定要区分出null属性和未初始化的lazy-loading属性。不然的话,有可能导致lazy-loading属性在数据库更新时被置空。
评论删除后,数据将无法恢复
评论(4)
引用来自“maverickpuss”的评论
技术每天都在进步, 然而最终盛行于世的不是那些最完美的, 而是最通用,成本可控, 老少咸宜的, 这也是为何windows在大众领域远胜于linux的原因.引用来自“maverickpuss”的评论
技术每天都在进步, 然而最终盛行于世的不是那些最完美的, 而是最通用,成本可控, 老少咸宜的, 这也是为何windows在大众领域远胜于linux的原因.