什么是反序列化和序列化
序列化合反序列化是什么呢,我们可以想象为比如我们从某宝买一张桌子或者柜子,这么大一个柜子肯定不会直接寄过来,那样太容易损坏而且也非常麻烦,一般来说的做法就是商家把桌子拆成一块块桌板寄过来,然后你自己拿到之后再进行拼接,那么商家在打包成木板的时候就是序列化的过程,反之你拆包装拼装桌子的时候就是反序列化的过程。
他的定义呢就是把一个Java类的一个实例序列化为数组(打包,方便传输),用于存储对象实例化信息:类成员变量合属性值。
因为Java序列化对象可以很方便的把对象转换成字节数组,又可以很方便的把字节数组反序列化成java对象,所以频繁的使用在Socket传输。在RMI和JMX服务中被强制性使用,在HTTP请求中也经常用到,如:直接接收序列化请求的后端服务、使用Base编码序列化字节字符串的方式传递等。
实现
在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)
或java.io.Externalizable(外部序列化)
接口即可被序列化,其中java.io.Externalizable
接口只是实现了java.io.Serializable
接口。
反序列化类对象时有如下限制:
- 被反序列化的类必须存在。
serialVersionUID
值必须一致。
除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了sun.reflect.ReflectionFactory.newConstructorForSerialization
创建了一个反序列化专用的Constructor(反射构造方法对象)
,使用这个特殊的Constructor
可以绕过构造方法创建类实例。
ObjectInputStream、ObjectOutputStream
java.io.ObjectOutputStream
类最核心的方法是writeObject
方法,即序列化类对象。
java.io.ObjectInputStream
类最核心的功能是readObject
方法,即反序列化类对象。
所以,只需借助ObjectInputStream
和ObjectOutputStream
类我们就可以实现类的序列化和反序列化功能了。
java.io.Serializable
实现了Serializable的接口类,需要产生一个serialVersionUID
常量,反序列化过程中如果双方的serialVersionUID
不一样会导致InvalidClassException
异常,如果可序列化类没有显示声明,在运行的时候会基于类的各个方法自动计算生成。
1 | package serializes; |
ObjectOutputStream
序列化类对象的主要流程是首先判断序列化的类是否重写了writeObject
方法,如果重写了就调用序列化对象自身的writeObject
方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息(通过反射获取所有不包含被transient
修饰的变量和值)。
自定义反序列化
实现了java.io.Serializable
接口的类,可以定义方法(魔术方法),会在反序列化或者序列化的时候调用
private void writeObject(ObjectOutputStream oos)
,自定义序列化。private void readObject(ObjectInputStream ois)
,自定义反序列化。private void readObjectNoData()
。protected Object writeReplace()
,写入时替换对象。protected Object readResolve()
。
具体的方法名定义在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
,其中方法有详细的声明。
当我们对DeserializationTest
类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)
方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)
方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObject
和writeObject
方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private
。