序列化与反序列化的单例模式

在上一篇文章中 ,我谈到了一般的序列化。 这是更加集中的内容,并提供了一个细节: 序列化代理模式 。 这是处理序列化中许多问题的一种好方法,通常是最好的方法。 如果开发人员只想了解这一主题,我会告诉他。

总览

这篇文章的重点是在给出两个简短的示例之前,最后介绍模式的详细定义,最后讨论其优缺点。

据我所知,该模式首先在约书亚·布洛赫(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 proxy's fields
}

创建原始类的实例将通过其常规API(例如,构造函数)完成。

人工字节流

由于writeReplace常规字节流将仅包含代理的编码。 但是对于人工流却并非如此! 它们可以包含原始实例的编码,并且由于反序列化这些序列未​​包括在模式中,因此它无法为这种情况提供任何保护措施。

实际上,对此类实例进行反序列化实际上是不需要的,必须防止。 这可以通过让原始类中的方法(在这种情况下被调用)抛出异常来完成:

防止直接反序列化原始实例

private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Proxy required.");
}

例子

以下示例是完整演示项目的摘录。 它们只显示多汁的部分,而忽略了一些细节(例如writeReplacereadObject )。

复数

一种简单的情况是复数的一种不变类型,称为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 ConcurrentMap<Class<?>, Object> cacheMap;

由于映射可以包含任意类型,因此并非所有映射都必须可序列化。 该类的合同规定,足以存储可序列化的类。 因此,有必要过滤地图。 代理的优点是它是所有此类代码的单点:

InstanceCache.SerializationProxy

private static class SerializationProxy implements Serializable {// array lists are serializableprivate final ArrayList<Serializable> serializableInstances;public SerializationProxy(InstanceCache cache) {serializableInstances = extractSerializableValues(cache);}private static ArrayList<Serializable> 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)的最后一句话:

总之,每当发现自己不得不在其客户端无法扩展的类上编写readObjectwriteObjet方法(用于自定义序列化形式)时,请考虑序列化代理模式。 这种模式可能是用非平凡的不变变量稳健地序列化对象的最简单方法。

有效的Java,第二版:p。 315

翻译自: https://www.javacodegeeks.com/2015/01/the-serialization-proxy-pattern.html

序列化与反序列化的单例模式

序列化与反序列化的单例模式_序列化代理模式相关推荐

  1. 序列化和反序列化的概念_序列化的概念

    序列化和反序列化的概念 讨论了为什么Optional不可序列化以及如何处理(即将推出)之后,让我们仔细看看序列化. 总览 这篇文章介绍了序列化的一些关键概念. 它尝试精简地执行此操作,而不会涉及太多细 ...

  2. python序列化和反序列化ppt_老生常谈Python序列化和反序列化

    通过将对象序列化可以将其存储在变量或者文件中,可以保存当时对象的状态,实现其生命周期的延长.并且需要时可以再次将这个对象读取出来.Python中有几个常用模块可实现这一功能. pickle模块 存储在 ...

  3. java中序列化与反序列化_Java中的序列化

    java中序列化与反序列化 Java提供了一种称为序列化的机制,以按字节的有序或字节序列的形式持久化Java对象,其中包括对象的数据以及有关对象的类型和存储在对象中的数据类型的信息. 因此,如果我们已 ...

  4. final类是否可以被代理_设计模式——代理模式

    代理模式 什么是代理模式 通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理.既(AOP微实现) ,AOP核心技术面向切面编程. 代理模式应用场景 SpringAOP ...

  5. Java中如何引用另一个类里的集合_【18期】Java序列化与反序列化三连问:是什么?为什么要?如何做?...

    Java序列化与反序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程: 序列化:对象序列化的最主要的用处就是在传递和保存对象 ...

  6. C#序列化与反序列化方式简单总结

    序列化和反序列化 相关类: System.SerializableAttribute特性(或称为属性), System.Runtime.Serialization.ISerializable(自定义序 ...

  7. 序列化和反序列化二叉树 -----前序,中序,后序,层序

    目录 一.序列化和反序列化 1.什么是序列化和反序列化 二.前序遍历 1.序列化 1.问题分析 2.代码实现 2.反序列化 1.问题分析 2.代码实现 三.后序遍历 1.序列化 1.思路分析 2.代码 ...

  8. golang力扣leetcode 297.二叉树的序列化与反序列化

    297.二叉树的序列化与反序列化 297.二叉树的序列化与反序列化 题解 代码 297.二叉树的序列化与反序列化 297.二叉树的序列化与反序列化 题解 题目:给你一个二叉树,序列化从一个string ...

  9. 十三、序列化和反序列化(部分转载)

    json和pickle序列化和反序列化 json是用来实现不同程序之间的文件交互,由于不同程序之间需要进行文件信息交互,由于用python写的代码可能要与其他语言写的代码进行数据传输,json支持所有 ...

最新文章

  1. cocos2dx[3.4](26)——视差节点ParallaxNode
  2. 023_JavaScript数字方法
  3. windows下用QTwebkit解析html
  4. 51nod 1448 二染色问题 (逆向考虑)
  5. 全地球的水也没办法将这个“特殊”的瓶子装满!
  6. 做梦也想有一个这样的实验室
  7. linux线程wait和sleep,java多线程 sleep()和wait()的区别
  8. 修改XP/Win7开机动画
  9. mysql数据库软件 国产_国产数据库发展情况如何?
  10. verilog 学习笔记2 异步复位串联T触发器
  11. js上传插件uploadify自动检测不到flash控件的问题
  12. 在日常生活中,简易合同的重要性 | 每天成就更大成功
  13. 函数u=(x,y,z)在点P处延方向向量n的方向导数的计算
  14. 一道逻辑推理题的程序实现(纯属娱乐)
  15. 【小学信息技术教资面试】教案模板
  16. HTML <kbd> 标签
  17. [易语言] 六边形扫雷游戏实战开发
  18. 什么是URL,URI或URN?
  19. nginx配置web项目外网访问
  20. 【运筹优化】结合天际线启发式的蚁群算法求解二维矩形装箱问题 + Java代码实现

热门文章

  1. 学习手记(2019/7/05~2019/8/31)——快乐暑假
  2. ssl1197-质数和分解【dp练习】
  3. [XSY4170] 妹子(线段树上二分)
  4. [XSY] 计数(DP,NTT,分治)
  5. Sentinel(十八)之注解支持
  6. Oracle的分页实现
  7. Redis 常用操作命令,非常详细
  8. 为什么说Java中只有值传递(另一种角度)
  9. FormData的使用
  10. 蓝桥杯JAVA省赛2013-----B------5(有理数类)