转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10857597.html
一:什么是序列化与反序列化
序列化:对象序列化是指将Java对象(动态的状态,如变量、函数)转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。
反序列化:从字节流重构出Java对象的过程。
序列化得到的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。
二:序列化的作用
1)对象持久化
我们知道,对象随着程序的运行而被创建,然后在不可达时被回收,生命周期是短暂的。但是如果我们想长久地把对象的内容保存起来怎么办呢?把它转化为字节序列保存在存储介质上即可。那就需要序列化。
2)网络传输对象
我们知道,两个进程之间通信时,传递的音频、视频等信息是以二进制序列形式来传输的。那么,对象也可以吗?可以,通过序列化把主机A进程上的对象序列化为二进制序列,传输到主机B上的进程从序列中重构出该对象。这在RMI中应用广泛,RMI的结果可以是一个对象。
3)进程间传递对象
三:如何序列化
1、让类对象可以被序列化
1)默认序列化方式
定义类时实现Serializable接口即可,这个Serializable接口是一个空接口,没有需要实现的方法。
作用是标记该类的对象可以被序列化,启用其序列化功能。通过调用 ObjectOutputStream和ObjectInputStream的方法来即可对该对象进行序列化和反序列化。
2)实现Serializable接口
定义类时,实现Serializable接口,并在类中重写两个序列化与反序列化接口,在其中定义对象序列化与反序列化动作。
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
这两个方法,在方法中通过对象输入流参数、对象输出流参数进行自定义的内容输出。
这样通过 ObjectOutputStream和ObjectInputStream 序列化和反序列化对象时会自动调用类中定义的writeObject、和readObject方法而不是默认的序列化和反序列化方法。
3)实现Externalnalizable接口
在类中实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,在方法中定义类对象自定义的序列化和反序列化操作。
这样通过对象输出流和对象输入流的输入输出方法序列化和反序列化对象时会自动调用类中定义的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。
2、序列化与反序列化API与使用过程
1)定义一个类,实现Serializable接口或者Externalizable接口,实现相应的序列化和反序列化方法(也可采取默认方法);
2)在程序代码中创建对象后,创建对象输出流ObjectOutputStream对象并在构造参数中指定流的输出目标(比如一个文件),通过objectOutputStream.writeObject(obj)把对象序列化并输出到流目标处;
3)在需要提取对象处:创建对象输入流ObjectInputStream对象并在构造参数中指定流的来源,然后通过readObject()方法获取对象,并通过强制类型转换赋值给类对象引用。
四:序列化底层原理
序列化算法会按步骤执行以下事情:
1)当前类描述的元数据输出为字节序列;【类定义描述、类中属性定义描述】
2)超类描述输出为字节序列;【如果超类还有超类,则依次递归,直至没有超类】
3)从最顶层超类往下,依次输出各类属性值描述,直至当前类对象属性值。
即:从下到上描述类定义,从上往下输出属性值。
五:序列化的特殊情况
1)静态变量和transient关键字修饰的变量不能被序列化;
2)反序列化时要按照序列化的顺序重构对象:如先序列化A后序列化B,则反序列化时也要先获取A后获取B,否则报错。
3)serialVersionUID(序列化ID)的作用:决定着是否能够成功反序列化。
虚拟机是否允许对象反序列化,不是取决于该对象所属类路径和功能代码是否与虚拟机加载的类一致,而是主要取决于对象所属类与虚拟机加载的该类的序列化 ID 是否一致。
java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
4)自定义序列化方法的应用场景:对某些敏感数据进行加密操作后再序列化;反序列化对加密数据进行解密操作。
5)重复序列化:同一个对象重复序列化时,不会把对象内容再次序列化,而是新增一个引用指向第一次序列化时的对象而已。
六:序列化破坏单例模式
见另一篇博文:https://www.cnblogs.com/ygj0930/p/10845530.html
七:为什么说反序列化并不安全——序列化漏洞[反序列化攻击]
反序列化是一系列安全问题的根源:攻击者能够将恶意数据序列化并存储到数据库或内存中,当应用进行反序列化时,应用会执行到恶意代码。
在谷歌内部,这个缺陷被称为“疯狂的小部件 (Mad Gadget)”,外界对它的叫法是 “Java 启示录 (Apocalypse)”。
怎样规避序列化漏洞:
1)对序列化对象执行完整性检查或加密,以防止恶意对象创建或数据篡改;最常见的例子之一就是JWT:JWT由3部分组成:Header,Payload,Verify Signature,最后的签名部分其实就是对数据进行完整性校验的关键部分,用secret对数据部分进行哈希计算,随后检查计算出来的哈希值是否和请求中的JWT签名部分的哈希值相同。若两者一致则认为数据完整性没有被破坏,若两者有差异则说明数据被修改过。
2)在创建对象之前强制执行严格的类型约束;
3)隔离反序列化的代码,使其在非常低的特权环境中运行;
4)记录反序列化的例外情况和失败信息,如:传入的类型不是预期的类型,或者反序列处理引发的例外情况;
5)限制或监视来自于容器或服务器传入和传出的反序列化网络连接;
6)监视反序列化,当用户持续进行反序列化时,对用户进行警告。
八:protobuf
protobuf:谷歌公司出的一款开源项目,序列号性能好,效率高,并且支持多种语言,例如:java,C++,python等。
protobuf3语言语法:https://blog.csdn.net/u011518120/article/details/54604615
protobuf3简单使用:https://blog.csdn.net/fangxiaoji/article/details/78826165
九:其他序列化方式对比
目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。
-
JSON:用途最广泛,序列化方式还衍生了阿里的fastjson,美团的MSON,谷歌的GSON等更加优秀的转码工具。
优点:使用方便。
缺点:数据冗长,转码性能一般。 -
XML:很久之前的转码方法,现在用的不多。
优点:暂时没发现。
缺点:数据冗长,转码性能一般。 -
Serialzable:JDK自带的序列化。
优点:使用方便。
缺点:无法跨语言、序列化的码流太大 、转码性能低下、存在安全漏洞。 -
hessian:基于 binary-RPC实现的远程通讯library,使用二进制传输数据。
优点:数据长度小。
缺点:性能低下。 - kryo:快速、高效的序列化框架。只能在java中使用,和前端非java语言的通讯存在极大的隔阂。
- protobuf:谷歌公司出的一款开源项目,转码性能高,支持多语言。