新的序列化可能会出现在Java中

OpenJDK网站出现了一个新的研究文档 ,该文档描述了将新的改进的序列化引入该语言以替代旧的语言的想法。

Java的序列化从版本1.1开始就存在,也就是说,几乎从它诞生之日起就已经存在。 一方面,序列化是一种非常方便的机制,通过从java.io.Serializable接口继承此类,您可以快速轻松地使任何类可序列化。 也许即使是这种简单性也已成为Java在世界范围内如此受欢迎的主要原因之一,因为它使您能够快速有效地编写网络应用程序。

另一方面,用Java实现序列化的方式涉及大量问题,这些问题增加了支持应用程序的成本,降低了应用程序的安全性并减慢了平台的发展。

Java中的序列化有什么问题? 我们列出了最严重的问题:

  • 序列化(和反序列化)绕过语言机制。 它忽略字段访问修饰符(私有,受保护)并在不使用构造函数的情况下创建对象,这意味着它忽略了这些构造函数中可能存在的不变式。 攻击者可以通过用无效数据替换数据来利用此漏洞,并且在反序列化期间可以成功吞噬它们。
  • 编写可序列化的类时,编译器无法提供任何帮助,也不会检测到错误。 例如,您不能静态保证可序列化类的所有字段本身都可序列化。 或者,您可以在readObject,writeObject,readResolve等方法的名称中输入错误,然后在序列化期间就不会使用这些方法。
  • 序列化不支持常规的版本控制机制,因此很难修改可序列化的类,以使其与旧版本兼容。
  • 序列化与流编码/解码紧密相关,这意味着很难将编码格式更改为与标准格式不同的格式。 另外,标准格式既不紧凑,也不高效,也不可读。

Java中现有序列化的根本错误是,它试图对程序员来说太“隐形”了。 它仅继承自java.io.Serializable,并接收由虚拟机执行的一些隐式魔术。
相反,程序员必须明确编写负责构造和解构对象的构造。 这些构造必须在语言级别,并且必须通过静态字段访问(而不是反射)来编写。

另一个序列化错误是它试图做太多事情。 它为自己设置了能够序列化任意对象图(可能包含循环)并反序列化它而不破坏其状态的任务。

可以通过简化任务和序列化对象(而不是对象树)而不是序列化数据树来纠正此错误,在该数据树中将没有身份的概念(如JSON)。

如何使序列化与对象模型自然匹配,使用构造函数进行反序列化,与编码格式分离并支持版本控制? 为此,可以使用注释,以及Java中尚未包含的语言的可能性: 模式匹配 。 例如:

public class Range { int lo; int hi; private Range(int lo, int hi) { if (lo > hi) throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); this.lo = lo; this.hi = hi; } @Serializer public pattern Range(int lo, int hi) { lo = this.lo; hi = this.hi; } @Deserializer public static Range make(int lo, int hi) { return new Range(lo, hi); } } 

在此示例中,声明了Range类,可以通过该类的两个特殊成员进行序列化:一个序列化程序和一个用@Serializer和@Deserializer批注标记的解串器。 序列化器是通过模式的解构函数实现的,而解串器是通过调用构造函数的静态方法实现的。 因此,在反序列化期间,不可避免地要检查构造函数中指定的不变hi> = lo。
这种方法没有魔术,并且使用常规注释,因此任何框架都可以进行序列化,而不仅仅是Java平台本身。 这意味着编码格式也可以绝对是任何格式(二进制,XML,JSON,YAML等)。

由于序列化器和反序列化器是常用方法,因此程序员在实现时具有很大的自由度。 例如,他可能选择的对象表示方式与对象在内存中的表示方式不同。 例如,LinkedList不能序列化为链接的链,而可以序列化为一个连续的数组,这将使表示更简单,更有效且更紧凑。

使用@Serializer和@Deserializer批注的特殊版本字段可实现此方法中的版本控制:

 class C { int a; int b; int c; @Deserializer(version = 3) public C(int a, int b, int c) { this a = a; this.b = b; this.c = c; } @Deserializer(version = 2) public C(int a, int b) { this(a, b, 0); } @Deserializer(version = 1) public C(int a) { this(a, 0, 0); } @Serializer(version = 3) public pattern C(int a, int b, int c) { a = this.a; b = this.b; c = this.c; } } 

在此示例中,取决于版本,将调用三个解串器之一。
如果我们不希望序列化器和反序列化器可用于序列化以外的任何人呢? 为此,我们可以将它们设为私有。 但是,在这种情况下,如果特定的序列化框架位于模块内部,而该模块未打开以进行深度反射访问,则特定的序列化框架将无法通过反射访问它们。 对于这种情况,建议在语言中引入另一种新结构:开放类成员。 例如:

 class Foo { private final InternalState is; public Foo(ExternalState es) { this(new InternalState(es)); } @Deserializer private open Foo(InternalState is) { this.is = is; } @Serializer private open pattern serialize(InternalState is) { is = this.is; } } 

在这里,序列化器和反序列化器用open关键字标记,这使它们对setAccessible打开。

因此,新方法从根本上不同于旧方法:在其中,类被设计为可序列化的,而不是照原样提供给平台。 这需要额外的精力,但会使序列化更可预测,更安全,并且与编码格式和序列化框架无关。

PS朋友,如果您想更快,更方便地收到有关Java的类似新闻,请在Telegram中订阅我的频道

Source: https://habr.com/ru/post/zh-CN455880/


All Articles