县网站建设,黑龙江跃众品牌策划公司,兰州响应式网站建设,广州企业建站网站序列化与反序列化的单例模式在上一篇文章中 #xff0c;我谈到了一般的序列化。 这是更加集中的内容#xff0c;并提供了一个细节#xff1a; 序列化代理模式 。 这是处理序列化中许多问题的一种好方法#xff0c;通常是最好的方法。 如果开发人员只想了解这一主题#xf… 序列化与反序列化的单例模式 在上一篇文章中 我谈到了一般的序列化。 这是更加集中的内容并提供了一个细节 序列化代理模式 。 这是处理序列化中许多问题的一种好方法通常是最好的方法。 如果开发人员只想了解这一主题我会告诉他。 总览 这篇文章的重点是在给出两个简短的示例之前最后介绍模式的详细定义最后讨论其优缺点。 据我所知该模式首先在约书亚·布洛赫Joshua Bloch的出色著作《 有效的Java》 第1版第57条第2版第78条 中定义。 这篇文章主要重申了那里的说法。 本文中使用的代码示例来自我在GitHub上创建的演示项目 。 查看更多详细信息 序列化代理模式 此模式应用于单个类并定义其序列化机制。 为了更容易阅读以下文本将分别将该类或其实例称为原始一个或多个实例。 序列化代理 顾名思义模式的关键是序列化代理 。 它被写入字节流而不是原始实例。 反序列化之后它将创建原始类的实例该类将在对象图中取代。 目的是设计代理使其成为原始类的最佳逻辑表示形式 。 实作 SerializationProxy是原始类的静态嵌套类。 它的所有字段均为final唯一的构造函数将原始实例作为唯一的参数。 它提取该实例状态的逻辑表示并将其分配给自己的字段。 由于原始实例被认为是“安全的”因此无需进行一致性检查或防御性复制。 原始类和代理类都实现Serializable。 但是由于前者实际上从未真正写入流中因此只有后者需要一个流唯一标识符 通常称为串行版本UID 。 序列化 当要对原始实例进行序列化时可以通知序列化系统将代理写入字节流。 为此原始类必须实现以下方法 用代理替换原始实例 private Object writeReplace() {return new SerializationProxy(this);
} 反序列化 在反序列化时必须反转从原始实例到代理实例的转换。 这是通过SerializationProxy中的以下方法实现的该方法在成功实例SerializationProxy代理实例后被调用 将代理转换回原始实例 private Object readResolve() {// create an instance of the original class// in the state defined by the proxys fields
} 创建原始类的实例将通过其常规API例如构造函数完成。 人工字节流 由于writeReplace常规字节流将仅包含代理的编码。 但是对于人工流却并非如此 它们可以包含原始实例的编码并且由于反序列化这些序列未包括在模式中因此它无法为这种情况提供任何保护措施。 实际上对此类实例进行反序列化实际上是不需要的必须防止。 这可以通过让原始类中的方法在这种情况下被调用抛出异常来完成 防止直接反序列化原始实例 private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException(Proxy required.);
}例子 以下示例是完整演示项目的摘录。 它们只显示多汁的部分而忽略了一些细节例如writeReplace和readObject 。 复数 一种简单的情况是复数的一种不变类型称为ComplexNumber 惊奇。 出于本示例的考虑它在其字段中存储了坐标以及极坐标形式据说是出于性能方面的考虑 ComplexNumber –字段 private final double real;
private final double imaginary;
private final double magnitude;
private final double angle; 序列化代理看起来像这样 ComplexNumber.SerializationProxy private static class SerializationProxy implements Serializable {private final double real;private final double imaginary;public SerializationProxy(ComplexNumber complexNumber) {this.real complexNumber.real;this.imaginary complexNumber.imaginary;}/*** After the proxy is deserialized, it invokes a static factory method* to create a ComplexNumber the regular way.*/private Object readResolve() {return ComplexNumber.fromCoordinates(real, imaginary);}
} 可以看出代理不存储极坐标形式的值。 原因是它应该捕获最佳的逻辑表示形式。 并且由于只需要一对值坐标或极坐标形式即可创建另一个因此仅一个序列化了。 这样可以防止存储两个对以实现更好的性能的实现细节通过序列化泄漏到公共API中。 请注意原始类和代理中的所有字段均为最终字段。 还要注意静态工厂方法的调用从而无需进行任何附加的有效性检查。 实例缓存 InstanceCache是一个异构类型安全的容器 它使用从类到其实例的映射作为后备数据结构 InstanceCache –字段 private final ConcurrentMapClass?, Object cacheMap; 由于映射可以包含任意类型因此并非所有映射都必须可序列化。 该类的合同规定足以存储可序列化的类。 因此有必要过滤地图。 代理的优点是它是所有此类代码的单点 InstanceCache.SerializationProxy private static class SerializationProxy implements Serializable {// array lists are serializableprivate final ArrayListSerializable serializableInstances;public SerializationProxy(InstanceCache cache) {serializableInstances extractSerializableValues(cache);}private static ArrayListSerializable extractSerializableValues(InstanceCache cache) {return cache.cacheMap.values().stream().filter(instance - instance instanceof Serializable).map(instance - (Serializable) instance).collect(Collectors.toCollection(ArrayList::new));}/*** After the proxy is deserialized, it invokes a constructor to create* an InstanceCache the regular way.*/private Object readResolve() {return new InstanceCache(serializableInstances);}}利弊 序列化代理模式减轻了序列化系统的许多问题。 在大多数情况下这是实现序列化的最佳选择并且应该是实现序列化的默认方法。 优点 这些是优点 减少语言外特征 该模式的主要优点是它减少了序列化的语言外特征 。 这主要是通过使用类的公共API创建实例来实现的请参见上面的SerializationProxy.readResolve 。 因此 每次创建实例都要经过构造函数并且始终会执行正确初始化实例所需的所有代码。 这也意味着在反序列化期间不必显式调用此类代码这可以防止其重复。 对最终字段没有限制 由于反序列化实例是在其构造函数中初始化的因此此方法不限制哪些字段可以是最终字段通常是使用自定义序列化形式的情况 。 灵活的实例化 实际上代理的readResolve不必返回与序列化类型相同的实例。 它也可以返回任何子类。 Bloch给出以下示例 考虑EnumSet的情况。 此类没有公共构造函数只有静态工厂。 从客户端的角度来看它们返回EnumSet实例实际上它们返回两个子类之一具体取决于基础枚举类型的大小。 如果基础枚举类型具有64个或更少的元素则静态工厂将返回RegularEnumSet 否则它们返回JumboEnumSet 。 现在考虑一下如果序列化其枚举类型具有60个元素的枚举集然后向该枚举类型添加另外五个元素然后反序列化该枚举集会发生什么情况。 序列化时它是一个RegularEnumSet实例但反序列化后最好是JumboEnumSet实例。 有效的Java第二版p。 314 代理模式使这个琐碎的事情变得很简单 readResolve仅返回匹配类型的实例。 这仅在类型符合Liskov替换原理的情况下有效 。 更高的安全性 它还极大地减少了防止用人工字节流进行某些攻击所需的额外思考和工作。 假设构造函数已正确实现。 符合单一责任原则 序列化通常不是类的功能要求但仍会极大地改变其实现方式。 这个问题无法消除但至少可以通过更好地分工来减轻。 让类做它的工作然后让代理处理序列化。 这意味着代理包含有关序列化的所有重要代码但仅包含其他内容。 与SRP一样 这大大提高了可读性。 关于序列化的所有行为都可以在一个地方找到。 而且序列化的表单也更容易发现因为在大多数情况下只需查看代理的字段即可。 缺点 Joshua Bloch描述了该模式的一些局限性。 不适合继承 它与客户端可扩展的类不兼容。 有效的Java第二版p。 315 是的就是这样。 没有进一步的评论。 我不太了解这一点但是我会发现更多… 圆形对象图的可能问题 它与某些对象图包含圆度的类不兼容如果尝试从对象的序列化代理的readResolve方法中调用对象上的方法则会得到ClassCastException 因为您还没有对象只有它序列化代理。 有效的Java第二版p。 315 性能 代理将构造函数执行添加到序列化和反序列化中。 布洛赫Bloch举例说明这台机器的价格要贵14。 当然这不是精确的度量但是证实了那些构造函数调用不是免费的理论。 反射 我们已经看到了序列化代理模式是如何定义和实现的以及它的优点和缺点。 应该清楚的是与默认和自定义序列化相比它具有一些主要优点应在适用时使用。 约书亚·布洛赫Joshua Bloch的最后一句话 总之每当发现自己不得不在其客户端无法扩展的类上编写readObject或writeObjet方法用于自定义序列化形式时请考虑序列化代理模式。 这种模式可能是用非平凡的不变变量稳健地序列化对象的最简单方法。 有效的Java第二版p。 315 翻译自: https://www.javacodegeeks.com/2015/01/the-serialization-proxy-pattern.html序列化与反序列化的单例模式