序列化与反序列化的单例模式_序列化代理模式

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

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

总览

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

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

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

相关文章

图解C语言的希尔排序

希尔排序是插入排序的一种&#xff0c;又称“缩小增量排序”&#xff0c;希尔排序是直接插入排序算法的一种更高效的改进版本。希尔排序的基本思想设等待排序等元素序列有n个元素&#xff0c;首先取一个整数increment&#xff08;小于n&#xff09;作为间隔将全部元素分为n/inc…

给oim_对OIM Web(UI)层进行压力测试

给oimOracle IDM中的默认配置保留20个专用于服务前端&#xff08;UI&#xff09;请求的线程 。 这基本上意味着应用程序服务器具有20个线程池&#xff0c;可用于为通过Web控制台&#xff08;/ identity或/ sysadmin&#xff09;访问OIM的用户提供服务。 对于Weblogic &#xf…

C语言打印输出红色字体

除了Linux&#xff0c;在VS下也可以实现变色这一效果&#xff0c;先看下面的一段代码&#xff1a;#include int main(int argc,char **argv){ printf("\033[44;37;5m hello world\033[0m\n");return 0;}编译后运行上述代码&#xff0c;结果如下&#xff1a;可见&…

mysql g月份分组_PowerBI快捷键——视觉对象分组功能

PowerBI的2020年4月份更新虽然发布在5月份&#xff0c;但的确是提供了很多强大的功能。在以往&#xff0c;要选中多个视觉对象&#xff0c;往往需要按住CTRL键挨个单击选中&#xff0c;然后在进行下一步的分组或其他操作。但是在4月份更新中&#xff0c;PowerBI允许我们通过在画…

编译原理抽象语法树_平衡抽象原理

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

谈谈单片机编程思想——状态机

玩单片机还可以&#xff0c;各个外设也都会驱动&#xff0c;但是如果让你完整的写一套代码时&#xff0c;却无逻辑与框架可言。这说明编程还处于比较低的水平&#xff0c;你需要学会一种好的编程框架或者一种编程思想&#xff01;比如模块化编程、状态机编程、分层思想等。本文…

C语言结构体使用与指针的理解

以前总有一种疑惑。为什么结构体的指针有的需要用分配空间&#xff1f;有的不需要分配空间呢&#xff1f;现在总结一下思路&#xff1a;一&#xff1a;关于结构体的定义问题&#xff1a;使用结构体一般会使用变量或者定义指针typedef struct{ int a; int b; }data;使用这个结构…

elementui 进度条怎么做_小E,Excel中这样的进度条是怎么做出来的?

我的目标&#xff1a;让中国的大学生走出校门的那一刻就已经具备这些office技能&#xff0c;让职场人士能高效使用office为其服务。支持我&#xff0c;也为自己加油&#xff01;前面我们分享过如何做进度条&#xff1a;《Excel进度条启示&#xff1a;专注与持续积累定会让人生出…

C语言结构体描述BMP的文件格式

BMP文件的结构其实非常简单&#xff0c;就是两个结构体&#xff0b;一个可选的调色板&#xff0b;位图数据。第一个结构体是BITMAPFILEHEADER&#xff0c;第二个结构体是BITMAPINFOHEADER。然后就是可选的调色板&#xff08;RGBQUAD数组&#xff09;。最后是位图数据。第一个结…

php mysql 常用语句_PHP mysql基本语句指令

1 /* 选择数据库 2 use test; 3 */ 4 5 /* 显示所有的数据库 6 show databases; 7 */ 8 9 /* 删除表/数据库 10 drop database test1; 11 delete from user1 where id4; 12 */ 13 14 /* 创建表 15 CREATE TABLE user1( 16 id int primary key auto_increment1 /*选择数据库2 us…

C语言Main函数到底有几种,你真的懂吗?

乍一看标题&#xff0c;感觉小编小题大做&#xff0c;但凡学过C语言的聚聚&#xff0c;都知道C程序入口就是main函数&#xff0c;且一套程序里面有且仅有一个。但是很多时候我们看到的main函数却并不是千篇一律&#xff0c;格式竟然会有差别&#xff0c;这究竟是为啥&#xff1…

python大型项目经验_图像分类:13个Kaggle项目的经验总结

来源&#xff1a;数据派THU任何领域的成功都可以归结为一套小规则和基本原则&#xff0c;当它们结合在一起时会产生伟大的结果。机器学习和图像分类也不例外&#xff0c;工程师们可以通过参加像Kaggle这样的竞赛来展示最佳实践。在这篇文章中&#xff0c;我将给你很多资源来学习…

C语言程序main入口函数

一.main()函数是什么样的我们先要搞清楚main()函数有哪几种&#xff1f;查阅C89/C99/C11标准文档&#xff0c;里面明确固定了两种写法&#xff1a;int main(void) { /* ... */ }int main(int argc, char *argv[]) { /* ... */ }除此之外&#xff0c;其他写法应该都是不规范的写…

spring可用于数据层吗_Spring XD用于数据提取

spring可用于数据层吗Spring XD是一个功能强大的工具&#xff0c;它是一组可安装的Spring Boot服务&#xff0c;可以独立运行&#xff0c;在YARN或EC2之上运行。 Spring XD还包括一个管理UI网站和一个用于作业和流管理的命令行工具。 Spring XD是一组功能强大的服务&#xff0c…

go语言mysql操作_使用Go语言操作MySQL数据库的思路与步骤

最近在做注册登录服务时&#xff0c;学习用Go语言操作MySQL数据库实现用户数据的增删改查&#xff0c;现将个人学习心得总结如下&#xff0c;另外附有代码仓库地址&#xff0c;欢迎各位有兴趣的fork。软件环境&#xff1a;Goland、Navicat for MySQL。一、实现思路1&#xff0c…

学习嵌入式C语言的6个层级,你在哪一层?

C语言可以说是一种经典的编程语言&#xff0c;没有C语言就没有今天的各种操作系统。C语言是基础&#xff0c;那么你掌握了多少&#xff1f;新手级别学习目的&#xff1a;过计算机二级&#xff0c;考证&#xff0c;应付期末考试。需要掌握的程度&#xff1a;掌握C语言的基本语法…

intellij idea_IntelliJ IDEA内部设计

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

C语言 | 函数执行成功时,return 1 还是return 0?

今天分享的内容是关于函数执行成功&#xff0c;返回0还是1的讨论~基本上&#xff0c;没有人会将大段的C语言代码全部塞入 main() 函数&#xff0c;更好的做法是按照复用率高&#xff0c;耦合性低的原则&#xff0c;尽可能的将代码拆分不同的功能模块&#xff0c;并封装成函数。…

jcache_窥探JCache API(JSR 107)

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

redis 启动加载mysql_Redis分析系列:启动加载过程

从本篇文章开始(命名为Redis分析系列)&#xff0c;将会通过分析Redis的源代码(以Redis 2.2.0 RC1为准)&#xff0c;来对它的内部实现做一些探讨。本文主要介绍Redis启动加载过程&#xff0c;总体上可以分为如下几步&#xff1a;1. 初始化全局服务器配置2. 加载配置文件(如果指定…