序列化代理模式

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

总览

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数分组学通MongoDB——第三天 细说高级操作

改章节个人在广东喝咖啡的时候突然想到的...明天就有想写几篇关于函数分组的文章&#xff0c;所以回家到后之就奋笔疾书的写出来发布了 明天跟大家分享一下mongodb中比拟好玩的识知&#xff0c;要主括包&#xff1a;聚合&#xff0c;标游。 一&#xff1a; 聚合 见常的聚合作操…

eps如何建立立体白模_服装立体裁剪教程 结构都是“立裁”出来的 才智服装

核心提示&#xff1a;基础立裁服装立体裁剪是指用白坯布为常用替代物&#xff0c;在人台上直接塑造服装样式&#xff0c;并进行样板制作的技术。由于立体裁剪是设计师主要依靠视觉进行的直观操作的过程&#xff0c;所以它具有激发和展开新的设计思维的功能。一、基础立裁服装立…

平衡抽象原理

使代码复杂易读和理解的一件事是&#xff0c;方法内部的指令处于不同的抽象级别。 假设我们的应用程序仅允许登录用户查看其朋友的旅行。 如果用户不是朋友&#xff0c;则不会显示任何行程。 一个例子&#xff1a; public List<Trip> tripsByFriend(User user, User l…

IntelliJ IDEA内部设计

IntelliJ IDEA的第一版于2001年1月发布&#xff0c;当时它是第一个集成了高级代码导航和代码重构功能的Java IDE之一。 2009年&#xff0c;JetBrains开源了其社区版本 。 从那时起&#xff0c;创建了许多基于它的IDE&#xff0c;例如Google的Android Studio。 让我们使用JArc…

TDD:MS自带的单元测试 之 线程模型和执行顺序

背景 我一直在呼喊“不要靠假设编程”&#xff0c;可是我却常常这么做。我用单元测试就是一种基于假设进行编程的反面教材&#xff0c;今天就下决心弄明白它。 主要想弄明白两个问题&#xff1a; 执行的所有单元测试方法的线程模型&#xff0c;是单线程&#xff1f;是多线程&am…

bat文件名操作_Excel按文件名制作目录,你复制粘贴花一小时,同事只要十秒搞定...

Excel有个特殊操作&#xff0c;那就是对文件夹中上百个文件&#xff0c;用Excel按照文件名制作目录&#xff0c;我们只需要点击对于的超链接就可以快速打开对于的文件。如上图所示&#xff0c;我们的文件夹中包含有26个视频和Excel文件&#xff0c;因为文件数量过多所以我们需要…

整数返回poj1005——I Think I Need a Houseboat

这两天一直在研究整数返回之类的问题,上午正好有机会和大家讨论一下. 原题&#xff1a; Description Fred Mapper is considering purchasing some land in Louisiana to build his house on. In the process of investigating the land, he learned that the state of Louisia…

偷窥JCache API(JSR 107)

这篇文章从较高的层次介绍了JCache API&#xff0c;并提供了一个预告片–刚够您&#xff08;希望&#xff09;开始对此发痒&#xff1b;-) 在这篇文章中...。 JCache概述 JCache API&#xff0c;实现 JCache API支持的&#xff08;Java&#xff09;平台 快速了解Oracle Coh…

canvas 圆角矩形填充_一篇文章让你学会你最“害怕”的Canvas,太有意思了

Canvas画布 基本用法<canvas idcanvas width"150" height"150"></canvas> <canvas>看起来跟img标签有点像&#xff0c;唯一不同的是它没有src属性和alt属性。实际上&#xff0c;canvas标签只有两个属性:width和height。 如果没有设置宽度…

abaqus单位_ANSYS和ABAQUS哪个好,一个例子告诉你

分别用ANSYS和ABAQUS来分析同一个题目并考察其异同点。【问题】一根悬臂梁&#xff0c;长200mm,截面是30mm*20mm的矩形(高度方向是20mm)。该梁左端固定&#xff0c;在其上面施加向下的分布力系&#xff0c;载荷集度是0.6Mpa.已知材料使用低碳钢&#xff0c;弹性模量是200GPA&am…

模型微调入门介绍一

备注&#xff1a;模型微调系列的博客部分内容来源于极客时间大模型微调训练营素材&#xff0c;撰写模型微调一系列博客&#xff0c;主要是期望把训练营的内容内化成自己的知识&#xff0c;我自己写的这一系列博客除了采纳部分训练营的内容外&#xff0c;还会扩展细化某些具体细…

npp夜光数据介绍 viirs_科研成果快报第177期:中国地区长时序AVHRR气溶胶数据的主要问题: 气溶胶反演频次与重污染天气...

中国地区长时序AVHRR气溶胶数据的主要问题&#xff1a;气溶胶反演频次与重污染天气A critical view of long-term AVHRR aerosol data record in China: Retrieval frequency and heavy pollution成果信息Minghui Tao, Rong Li, Lili Wang et al. (2020)A critical view of lon…

使用Eclipse创建一个Android程序方法

要编写Android程序&#xff0c;需要安装JDK、Eclipse和Android SDK。 Android SDK的安装路径不要在program file或program file(x86)下&#xff0c;否则在debug时会碰很奇怪的问题。最好直接放在C:\Android下。&#xff08;如果非要放在Program files下也可以&#xff0c;在ecl…

如何使用Hibernate批处理DELETE语句

介绍 在我以前的文章中 &#xff0c;我解释了批处理INSERT和UPDATE语句所需的Hibernate配置。 这篇文章将继续本主题的DELETE语句批处理。 领域模型实体 我们将从以下实体模型开始&#xff1a; Post实体与Comment具有一对多关联&#xff0c;并且与PostDetails实体具有一对一…

蓝点linux_新闻速读 gt; Windows 10 的 Linux 内核将像驱动程序一样由微软更新服务进行更新 | Linux 中国...

本文字数&#xff1a;3252&#xff0c;阅读时长大约&#xff1a;4 分钟导读&#xff1a;• Ubuntu 发行商 Canonical 将参加微软欧洲虚拟开源峰会 • 树莓派支持 Vulkan 最新进展&#xff1a;通过 70000 项测试 • 谷歌浏览器开始隐藏 URL 详细路径&#xff0c;未来地址栏将只显…

struts2-通配符和动态方法调用

通配符举例--BookAction 1 public class BookAction extends ActionSupport {2 3 public String execute() throws Exception {4 System.out.println("BookAction ********** execute()");5 return null;6 }7 /*8 * 显示图书添加页…

JavaFX技巧18:路径剪切

我最近注意到&#xff0c;我致力于ControlsFX项目的PopOver控件无法正确剪切其内容。 当我为FlexCalendarFX框架开发手风琴弹出窗口时&#xff0c;这一点变得显而易见。 每当最后一个标题窗格扩展时&#xff0c;其底角不再是圆角而是正方形。 在将红色矩形作为内容放置到标题窗…

关于erlang的套接字编程

套接字编程即熟悉的Socket编程&#xff0c;根据传输层协议&#xff0c;可分为&#xff1a;UDP协议和TCP协议.下面写一个简单的例子&#xff0c;再重新认识下它&#xff1a; 1.在同一主机节点下启动两个Erlang节点. a).在第一个Erlang节点下&#xff0c;打开端口为1234的UDP套接…

kotlin 添加第一个 集合_Flutter开发必学Dart语法篇之集合操作符函数与源码分析...

简述:在上一篇文章中&#xff0c;我们全面地分析了常用集合的使用以及集合部分源码的分析。那么这一节讲点更实用的内容&#xff0c;绝对可以提高你的Flutter开发效率的函数&#xff0c;那就是集合中常用的操作符函数。这次说的内容的比较简单就是怎么用&#xff0c;以及源码内…

在Java中确定文件类型

以编程方式确定文件的类型可能非常棘手&#xff0c;并且已经提出并实现了许多基于内容的文件标识方法。 Java中有几种可用于检测文件类型的实现&#xff0c;其中大多数很大程度上或完全基于文件的扩展名。 这篇文章介绍了Java中最常见的文件类型检测实现。 本文介绍了几种在Ja…