Effective-java-读书笔记之序列化

第85条 优先考虑非Java序列化的其他选择

Java的序列化容易被黑客利用, 引发安全问题.

deserialization bombs: 反序列化它将花费很长时间, 或永远无法完成.

避免序列化漏洞被利用的最佳方法是永远不要反序列化任何东西。

有很多机制可以替代序列化, 并且提供更多好处, 比如跨平台, 高性能, 社区支持等.

本书把这些替代方式称作cross-platform structured-data representations.

比较流行的有:

  • JSON
  • Protocol Buffers (protobuf).

如果你不能完全地避免Java序列化, 你可以:

  • 永远不要反序列化不受信任的数据.
  • 如果对数据安全性不能完全确定, 使用Java 9的java.io.ObjectInputFilter在反序列化前过滤数据(优先使用白名单策略).

第86条 谨慎地实现Serializable接口

实现Serializable不是一个轻率的决定.

实现Serializable接口而付出的最大代价是, 一旦一个类被发布, 就大大降低了"改变这个类的实现"的灵活性.

如果你接受了默认的序列化形式, 这个类中私有的和包级私有的实例域将都变成导出的API的一部分, 这不符合"最低限度地访问域"的实践原则, 从而它就失去了作为信息隐藏工具的有效性.

序列化会使类的演受到限制, 这种限制的一个例子与流的唯一标识符有关, 通常它也被称为序列版本UID(serial version UID). 如果没有显式声明, 系统会自动生成.

实现Serializable的第二个代价是, 它增加了出现Bug和安全漏洞的可能性. -> 反序列化是一个隐藏的构造器.

实现Serializable的第三个代价是, 随着类发行新的版本, 相关的测试负担也增加了.

为了继承而设计的类应该尽可能少地去实现Serializable接口, 用户的接口也应该尽可能少地继承Serializable接口.

内部类不应该实现Serializable接口, 除非是静态内部类.

第87条 考虑使用自定义的序列化形式

如果没有先认真考虑默认的序列化形式是否合适, 则不要贸然接受.

如果一个对象的物理表示法等同于它的逻辑内容, 可能就适合于使用默认的序列化形式.

即使你确定了默认的序列化形式是合适的, 通常还必须提供一个readObject方法以保证约束关系和安全性.

当一个对象的物理表示法与它的逻辑数据内容有实质性的区别时, 使用默认序列化形式会有以下4个缺点:

  • 它使这个类的导出API永远地束缚在该类的内部表示法上.
  • 消耗过多的空间.
  • 消耗过多的时间.
  • 会引起栈溢出.

transient修饰符: 从序列化形式中省略掉实例域. 反序列化时这些域将被初始化为默认值.

当你决定把一个字段标记为非transient之前, 首先需要说服自己, 这个字段是这个对象逻辑状态的一部分.

无论你是否使用默认的序列化形式, 如果在读取整个对象状态的任何其他方法上强制任何同步, 则也必须在对象序列化上强制这种同步.

不论你选择了哪种序列化形式, 都要为自己编写的每个可序列化的类声明一个显式的序列版本UID(serial version UID).

除非你要破坏和所有已经存在的实例的兼容性, 否则就不要改序列版本UID.

第88条 保护性地编写readObject方法

readObject方法实际上相当于一个公有的构造器, 如同其他的构造器一样, 它也要求注意同样的所有注意事项.

构造器必须检查其参数的有效性, 并且在必要的时候对参数进行保护性拷贝.

编写更加健壮的readObject()方法的指导方针:

  • 对于对象引用域必须保持为私有的类, 要保护性地拷贝这些域中的每个对象. 不可变类的可变组件就属于这一类别.
  • 对于任何约束条件, 如果检查失败, 则抛出一个InvalidObjectException异常. 这些检查动作应该跟在所有的保护性拷贝之后.
  • 如果整个对象图在被反序列化之后必须进行验证, 就应该使用ObjectInputValidation接口.
  • 无论是直接还是间接方式, 都不要调用类中任何可被覆盖的方法.

第89条 对于实例控制, 枚举类型优先于readResolve

如果单例模式的类加上了implements Serializable, 就多了一种创建实例的途径.

readResolve特性允许你用readObject创建的实例代替另一个实例.

对于一个正在被反序列化的对象, 如果它的类定义了一个readResolve方法, 并且具备正确的声明, 那么在反序列化之后, 新建对象上的readResolve方法就会被调用.

然后该方法返回的对象引用将被返回, 取代新建的对象. 在这个特性的绝大多数用法中, 指向新建对象的引用不需要再被保留, 因此立即成为垃圾回收的对象.

可以利用readResolve方法保证单例模式. -> 方法忽略被反序列化的对象, 只返回该类初始化时创建好的那个实例.

如果依赖readResolve进行实例控制, 带有对象引用类型的所有实例域都必须声明为transient的.

从历史上来看, readResolve方法被用于所有可序列化的实例受控(instance-controlled)的类. 自从Java1.5以来, 它就不再是在可序列化的类中维持实例控制的最佳方法了.

应该尽可能地使用枚举类型来实施实例控制的约束条件.

但是如果这不可能做到, 或者你需要一个实现了序列化的实例受控的类, 那么你就必须提供一个readResolve方法, 然后确保所有的字段都是primitive或transient的.

第90条 考虑用序列化代理代替序列化实例

序列化代理模式(serialization proxy pattern):

  • 为可序列化的类设计一个私有的静态嵌套类(序列化代理), 它应该有一个单独的构造器, 其参数类型就是那个外围类.
  • 在外围类中添加writeReplace方法. -> 产生代理类实例.
  • 外围类中添加readObject方法. -> 防止伪造.
  • 代理类中提供readResolve方法, 返回一个逻辑上相当的外围类的实例. -> 序列化代理转变回外围类的实例.

序列化代理模式的局限性:

  • 不能与可以被客户端扩展的类兼容.
  • 不能与对象图中包含循环的某些类兼容.
  • 序列化代理模式的功能和安全性有性能开销的代价.

总而言之, 每当你发现自己必须在一个不能被客户端扩展的类上编写readObject或者writeObject方法的时候, 就应该考虑使用序列化代理模式.

原文地址:https://cloud.tencent.com/developer/article/2138534

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = "hello"; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let's briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越