Java序列化-实现细节泄漏

Java序列化-实现细节泄漏,java,serialization,Java,Serialization,在一次谈话中,Joshua Bloch谈到了“实现细节泄漏” “Java中一个非常臭名昭著的例子是,如果你简单地说,“实现” 一旦你做到了这一点,你的整个实现就完成了 只是作为API的一部分泄漏了出来,因为串行表单 包含组成对象的整个字段,甚至包括 私有字段,所以私有字段突然成为 公共API,这真的,真的很糟糕。还有解决方法 顺便说一句,要仔细设计你的系列表格,不要只是说 实现可序列化。” 如果你们能帮我理解他的意思 顺便说一下,解决这个问题的方法是设计你的系列表单 小心不要只说实现可序列化 通

在一次谈话中,Joshua Bloch谈到了“实现细节泄漏”

“Java中一个非常臭名昭著的例子是,如果你简单地说,“实现” 一旦你做到了这一点,你的整个实现就完成了 只是作为API的一部分泄漏了出来,因为串行表单 包含组成对象的整个字段,甚至包括 私有字段,所以私有字段突然成为 公共API,这真的,真的很糟糕。还有解决方法 顺便说一句,要仔细设计你的系列表格,不要只是说 实现可序列化。”

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

顺便说一下,解决这个问题的方法是设计你的系列表单 小心不要只说实现可序列化

通过封装,我们假装没有透露对象的内部表示,我们只通过组件的公共接口与组件交互;当我们希望在不破坏组件用户的任何代码的情况下更改组件中数据的内部表示形式时,通常会利用该属性

相反,序列化意味着通过将对象的状态转换为其他格式来公开对象的内部状态,这些格式可以在以后存储和恢复。这意味着,一旦序列化,对象的内部结构就无法更改,而不会危及此恢复过程的成功

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

例如,我们有一个班级成员,如下所示:

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

但是,考虑到类人是默认可串行化的,如果我们的系统是一个开放系统,那么可能有数千行代码,它们依赖于这样一个事实,即它们能够基于原始类重启序列化对象,甚至可能是基于类的原始版本作为其父类序列化扩展类的客户机。这些对象中的一些可能已经被我们的API用户序列化为二进制形式或其他格式,他们现在希望发展到代码的第二个版本

然后,如果我们想做一些改变,就像我们在第二个例子中所做的那样,我们会立即打破其中的一些;所有拥有基于类原始版本的序列化对象的人,他们存储的对象包含一个名为age of type int的字段,该字段包含一个人的年龄,在这些对象的反序列化过程中,包含性别信息的布尔类型的名为isMale的字段可能会失败,因为新的类定义使用了新的字段和新的数据类型

显然,我们这里的问题是序列化暴露了关于对象的敏感信息,现在我们不能简单地更改任何内容,甚至不能更改我们认为已封装的内容,因为通过序列化,所有内容都已公开

有很多方法可以处理可序列化类的演变,但这里的重点是,在封装方面,我们希望尽可能地包含可序列化类,对于那些确实需要序列化的类,然后,我们可能需要考虑任何可能的场景的含义,在这些场景中,我们可能尝试使用对象类的演化版本来复活对象

Joshua Block的建议是,如果你想促进封装,也许你想要的是序列化其他东西,也许不是你对象的所有内部状态,或者为了序列化的目的设计一些完全不同的东西,另一个类,它不会公开对象的很多内部状态,也不会干扰序列化过程(writeObject、readObject),以确定要准确序列化的内容


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

您希望仅在必要时实现可序列化。有些系统错误地将用户凭据序列化为自定义主体,这将贯穿始终