Java序列化 - 实现细节泄漏

时间:2014-05-10 12:29:33

标签: java serialization

Joshua Bloch谈到的其中一个演讲"实施细节泄漏"

  

" Java中一个非常臭名昭着的例子,如果你只是说," Implements   。序列"完成后,您的整个实施都已完成   只是作为API的一部分泄露出来,因为串行表单   包括整个领域,甚至包括你的对象   私人领域,所以突然私人领域是其中的一部分   公共API,这真的非常糟糕。顺便说一下   顺便说一下,仔细设计你的连续形式。不要说   实现可序列化。"

如果你们能帮我理解他的意思

  

顺便说一下,设计串行表格的方法就是这样   小心。不要说工具可序列化。

1 个答案:

答案 0 :(得分:2)

使用封装,我们假装没有透露关于对象的内部表示,并且我们仅通过其公共接口;当我们想要更改组件中的数据的内部表示而不破坏其用户的任何代码时,我们通常会利用的理想属性。

相反,序列化意味着通过将对象的状态转换为可以在以后存储和复活的其他格式来暴露对象的内部状态。这意味着,一旦序列化,就不能改变对象的内部结构而不会冒这个复活过程成功的风险。

序列化的问题不仅出现在开放系统的情况下,而且出现在以某种方式依赖它的分布式系统中。例如,如果我们停止我们的应用程序服务器,它可能会选择序列化当前会话中的对象以便在服务器重新启动时重新生成它们,但是如果我们使用新版本的可序列化对象重新部署我们的应用程序,它们是否仍然是服务器试图复活它们时兼容吗?在分布式系统中,使用code mobility是常见的,即,类集合位于中央存储库中,可供客户端和服务器共享公共代码。在这种方法中,由于对象被序列化以在客户端和服务器之间共享,如果我们更新此公共存储库中的可序列化类,我们是否有可能破坏任何内容?

例如,考虑我们有一个类Person,如下所示:

public class Person {
   private String firstName;
   private String lastName;
   private boolean isMale;
   private int age;

  public boolean isMale() {
     return this.isMale;
  }
  public int getAge() {
    return this.age;
  }
  //more getters and setters
}

让我们说我们发布了我们的API的第一个版本,带有Person的抽象。但是对于第二个版本,我们想介绍两个变化:首先,我们发现如果我们可以存储一个人的出生日期而不是整数的年龄会更好,然后我们定义当Java没有枚举但是现在我们想用它们代表一个人的性别时,可能发生了类。

显然,由于字段已正确封装,我们可以在不影响公共接口的情况下更改类的内部工作方式。有点像这样:

public class Person {
   private String firstName;
   private String lastName;
   private Gender gender;
   private Date dateOfBirth;

  public boolean isMale() {
     return this.gender == Gender.MALE;
  }
  public int getAge() {
    Calendar today = Calendar.getInstance();
    Calendar birth = Calendar.getInstance();
    birth.setTime(this.dateOfBirth);
    return today.get(Calendar.YEAR) - birth.get(Calendar.YEAR);
  }
  //the rest of getters and setters
}

通过如上所示进行这些更改,我们可以确保预先存在的客户端不会中断,因为即使我们更改了对象状态的内部表示,我们也保持公共接口不变。

但是,考虑到默认情况下Person类是可序列化的,如果我们的系统是一个开放系统,那么可能有数千行代码依赖于它们能够基于它复活序列化对象的事实。原始类,或者甚至是基于类的原始版本作为其父类序列化扩展类的客户端。其中一些对象可能已被我们的API用户序列化为二进制形式或其他格式,现在,他们希望演变为我们的第二版代码。

然后,如果我们想要像我们在第二个例子中那样做一些改变,我们会立即打破其中一些;所有具有序列化对象的人都是基于类的原始版本,这些对象包含一个包含一个名为age of int的字段的对象,包含一个人的年龄,以及一个名为isMale的字段,其类型为boolean,包含有关性别的信息,可能会失败这些对象的反序列化是因为新的类定义使用了新的字段和新的数据类型。

显然我们的问题是序列化暴露了有关我们对象的敏感信息,现在我们不能简单地改变任何东西,甚至不是我们认为封装的东西,因为通过序列化,一切都已公开曝光。

有很多方法可以处理可序列化类的发展,但重要的一点是,当涉及到封装时,我们希望尽可能包含可序列化的类以及我们确实需要的那些类。序列化,然后我们可能需要思考任何可能的场景的含义,在这些场景中我们可能尝试使用其类的进化版本来复活对象。

所以,Joshua Block似乎建议的是,如果你想要促进封装,也许,你想要的是序列化其他东西,也许不是你对象的所有内部状态,或者为了序列化目的设计一些完全不同的东西,另一个类不会暴露对象的内部状态或干扰序列化过程(writeObject,readObject)来决定什么将被准确地序列化。

更糟糕的是,当一个对象被序列化时,它还会序列化其图形中的其他对象(如果它们是可序列化的),这会使它们暴露于这个问题。这就是为什么Josua说" [...]对象的理想序列化形式只包含对象所代表的逻辑数据"。