Java中的序列化
概念
把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
序列化与反序列化
Java的序列化与反序列化主要靠ObjectOutputStream类和ObjectInputStream类完成,具体使用看代码:
ObjectOutputStream oops = new ObjectOutputStream(new FileOutputStream("G:/serializable.txt"));
oops.writeObject(new Test());
ObjectInputStream oips = new ObjectInputStream(new FileInputStream("G:/serializable.txt"));
Object object = oips.readObject();
讲讲几个需要注意的地方:
1.上面代码演示的Test类必须实现序列化接口,这里有两个接口可以实现,分别是Serializable和它的子接口Externalizable,下面做详细讲解;
2.序列化的对象其属性必须也可以被序列化,即也实现序列化接口,***static变量和transient关键字修饰的变量不会被序列化(前提是使用默认的序列化机制)***,因此如果不想让某些变量序列化可以这么做,基本类型默认可以序列化。
看Test类中存在属性的情况:
class A {
public int i = 1;
}
public class Test implements Serializable{
public A a;
public Test(A a){
this.a = a;
}
public static void main(String[] args) throws Exception {
ObjectOutputStream oops = new ObjectOutputStream(new FileOutputStream("G:/serializable.txt"));
oops.writeObject(new Test(new A()));
}
}
//console:
Exception in thread "main" java.io.NotSerializableException: com.security.serializable.A
这里有一个误区:如果声明的类没有实现序列化接口,但是给它赋值的对象实现了也是可以的,主要还是看具体的对象,像这样
class A {
public int i = 1;
}
class B extends A implements Serializable{}
public class Test implements Serializable{
public A a;
public Test(A a){
this.a = a;
}
public static void main(String[] args) throws Exception {
ObjectOutputStream oops = new ObjectOutputStream(new FileOutputStream("G:/serializable.txt"));
oops.writeObject(new Test(new B()));
}
}
//console:
3.要序列化的对象的父类如果实现序列化接口,那么也会同时序列化其父类,反序列化时不会调用构造函数;没有实现的话就不会序列化父类,但是在反序列化的时候会调用父类中满足条件的无参构造方法进行创建对象,因为要实例化子类必须先实例化其父类;
class A {
public A(){
System.out.println("父类的无参构造");
}
public A(int i){
System.out.println("父类的有参构造");
}
}
public class Test extends A implements Serializable{
public Test(){
System.out.println("子类的无参构造");
}
public Test(int i){
super(i);
System.out.println("子类的有参构造");
}
public static void main(String[] args) throws Exception {
ObjectOutputStream oops = new ObjectOutputStream(new FileOutputStream("G:/serializable.txt"));
oops.writeObject(new Test(1));
ObjectInputStream oips = new ObjectInputStream(new FileInputStream("G:/serializable.txt"));
Object object = oips.readObject();
}
}
//output:
父类的有参构造
子类的有参构造
父类的无参构造
4.反序列化时读取的顺序要和序列化时保存的顺序一致。
源码分析
序列化
- ObjectStreamClass类
官方文档对这个类的介绍如下
Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.
可以看到ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。
2. 序列化:writeObject()
在调用writeObject()进行序列化之前会先调用ObjectOutputStream的构造函数生成一个ObjectOutputStream对象,构造函数如下:
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
// bout表示底层的字节数据容器
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();// 写入文件头
bout.setBlockDataMode(true);// flush数据
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
构造函数中首先会把bout绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法,
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
在writeStreamHeader()方法中首先会往底层字节容器中写入表示序列化的Magic Number以及版本号,接下来会调用writeObject()方法进行序列化,
public final void writeObject(Object obj) throws IOException {
...
// 开始序列化
writeObject0(obj, false);
...
}
一般会调用writeObject0()进行序列化操作,
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {
...
Object orig = obj;
// 获取要序列化的对象的Class对象
Class cl = obj.getClass();
ObjectStreamClass desc;
//这是一个循环,目的是判断writeReplace()返回的对象中是否还有这个方法,一直到最后需要序列化的那个对象,并返回这个对象。
//这就是我们可以自定义具体序列化哪个对象!!!
for (;;) {
Class repCl;
// 创建描述cl的ObjectStreamClass对象!这也是重点下面分析
desc = ObjectStreamClass.lookup(cl, true);
//如果有writeReplace()就通过反射执行,方法会返回一个Object对象,如果返回的这个对象的类和本类相同,就不重复执行writeReplace()
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
}
...
//如果是字符串、数组、枚举、实现了Serializable接口的对象可以序列化,其余的不可以!!!
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) {
// 被序列化对象实现了Serializable接口
writeOrdinaryObject(obj, desc, unshared);
}
...
}
这里解释了为什么需要序列化的对象只要实现Serializbale接口就能够进行序列化。
desc = ObjectStreamClass.lookup(cl, true);
lookup()主要为了创建一个描述对象,看看它的构造方法:
private ObjectStreamClass(final Class<?> cl) {//cl为需要序列化对象的类对象
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);111获取serialVersionUID
try {
//获取实现Serializable接口对象的字段,实现Externalizable的为空字段
//没有声明序列化字段的,则获取默认的序列化字段!!!就是除了static和transient修饰的其他字段
fields = getSerialFields(cl);222
computeFieldOffsets();//Calculates and sets serializable field offsets
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
//!!!!看它源码
cons = getExternalizableConstructor(cl);333
} else {
//!!!!看它源码
cons = getSerializableConstructor(cl);444
//定义了writeObject、readObject、readObjectNoData
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);555
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
//定义writeReplace、readResolve
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);666
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError();
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
}
111:getDeclaredSUID()方法,通过反射拿到声明的serialVersionUID字段的值,并且这个字段必须是static final修饰的:
private static Long getDeclaredSUID(Class<?> cl) {
try {
Field f = cl.getDeclaredField("serialVersionUID");
int mask = Modifier.STATIC | Modifier.FINAL; //int mask = 8 | 16
if ((f.getModifiers() & mask) == mask) {
f.setAccessible(true);
return Long.valueOf(f.getLong(null));
}
} catch (Exception ex) {
}
return null;
}
如果未声明serialVersionUID又对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
显式地定义serialVersionUID有两种用途:
1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
222:
private static ObjectStreamField[] getDeclaredSerialFields(Class<?> cl)
throws InvalidClassException
{
ObjectStreamField[] serialPersistentFields = null;
try {
Field f = cl.getDeclaredField("serialPersistentFields");
int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
if ((f.getModifiers() & mask) == mask) {
f.setAccessible(true);
serialPersistentFields = (ObjectStreamField[]) f.get(null);
}
} catch (Exception ex) {
}
if (serialPersistentFields == null) {
return null;
} else if (serialPersistentFields.length == 0) {
return NO_FIELDS;
}
ObjectStreamField[] boundFields =
new ObjectStreamField[serialPersistentFields.length];
Set<String> fieldNames = new HashSet<>(serialPersistentFields.length);
for (int i = 0; i < serialPersistentFields.length; i++) {
ObjectStreamField spf = serialPersistentFields[i];
String fname = spf.getName();
//不能重复定义相同的字段
if (fieldNames.contains(fname)) {
throw new InvalidClassException(
"multiple serializable fields named " + fname);
}
fieldNames.add(fname);
try {
Field f = cl.getDeclaredField(fname);
if ((f.getType() == spf.getType()) &&
((f.getModifiers() & Modifier.STATIC) == 0))//声明指定要序列化的字段必须类型和名字要和实际的对应,且不能是static字段
{
boundFields[i] =
new ObjectStreamField(f, spf.isUnshared(), true);
}
} catch (NoSuchFieldException ex) {
}
//如果声明的字段不存在,或与实际字段类型不符,或为static字段,
if (boundFields[i] == null) {
boundFields[i] = new ObjectStreamField(
fname, spf.getType(), spf.isUnshared());
}
}
return boundFields;
}
先获取声明的private static final ObjectStreamField[] serialPersistentFields = …;如果未声明就去获取默认的序列化字段。换句话说如果声明了这个字段,就不会去序列化类中的属性字段。
static和transient字段不能被序列化。序列化的时候所有的数据都是来自于ObjectStreamClass对象
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;//int mask = 8|128;
for (int i = 0; i < clFields.length; i++) {
// 说明字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉。
333:
private static Constructor getExternalizableConstructor(Class<?> cl) {
try {
Constructor cons = cl.getDeclaredConstructor((Class<?>[]) null);1
cons.setAccessible(true);
return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
cons : null;2
} catch (NoSuchMethodException ex) {
return null;
}
}
Modifier.PUBLIC = 1,从1和2可以知道一个实现Externalizable接口的类要有一个公有的无参构造函数,主要在反序列化的时候需要。
444:一个类实现了Serializable但未实现Externalizable时,获取第一个未实现序列化接口的类的无参构造函数,修饰符满足下面条件,一定需要一个非私有的无参构造函数
private static Constructor getSerializableConstructor(Class<?> cl) {
Class<?> initCl = cl;
//找到其父类中第一个未实现Serializable接口的类(Object类的父类为空)
while (Serializable.class.isAssignableFrom(initCl)) {
if ((initCl = initCl.getSuperclass()) == null) {
return null;
}
}
try {
Constructor cons = initCl.getDeclaredConstructor((Class<?>[]) null);
int mods = cons.getModifiers();
//父类的无参构造函数不能是private的,可以是public和protected的,如果是默认的访问修饰符,那么一定需要和当前序列化对象在同一个包下
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
//返回ture,如果这个父类和当前对象在同一个包下,否则返回false
!packageEquals(cl, initCl)))
{
return null;
}
cons = reflFactory.newConstructorForSerialization(cl, cons);
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;
}
}
private static Method getPrivateMethod(Class<?> cl, String name,
Class<?>[] argTypes,
Class<?> returnType)
{
try {
Method meth = cl.getDeclaredMethod(name, argTypes);
meth.setAccessible(true);
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType) &&
((mods & Modifier.STATIC) == 0) &&
((mods & Modifier.PRIVATE) != 0)) ? meth : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
方法是private的,返回类型为returnType,且是非static。
private void writeObject(ObjectOutputStream out)
private void readObject(ObjectInputStream in)
private void readObjectNoData()
private static Method getInheritableMethod(Class<?> cl, String name,
Class<?>[] argTypes,
Class<?> returnType)
{
Method meth = null;
Class<?> defCl = cl;
//从本类开始向上一直找这个方法
while (defCl != null) {
try {
meth = defCl.getDeclaredMethod(name, argTypes);
break;
} catch (NoSuchMethodException ex) {
defCl = defCl.getSuperclass();
}
}
if ((meth == null) || (meth.getReturnType() != returnType)) {
return null;
}
meth.setAccessible(true);
int mods = meth.getModifiers();
//方法不能是static和abstract的
if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
return null;
//可以是public和protected修饰的
} else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
return meth;
//如果修饰符是private的,那这个方法必须在序列化对象的当前类中
} else if ((mods & Modifier.PRIVATE) != 0) {
return (cl == defCl) ? meth : null;
} else {
//如果采用默认修饰符,那么这个方法所在的类必须和序列化对象的当前类在同一个包下
return packageEquals(cl, defCl) ? meth : null;
}
}
两种事例:
public Object writeReplace()
private Obejct readResolve()
序列化过程接下来会执行到writeOrdinaryObject()这个方法中,
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared) throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT); // 写入Object标志位
writeClassDesc(desc, false); // 写入类元数据
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);//写入实现Externalizable接口的对象
} else {
writeSerialData(obj, desc); // 写入被序列化的对象的实例数据
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object
/**
* new Object.
*/
final static byte TC_OBJECT = (byte)0x73;
接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下:
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
// 如果desc为null
writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
writeHandle(handle);
} else if (desc.isProxy()) {
writeProxyDesc(desc, unshared);
} else {
writeNonProxyDesc(desc, unshared);
}
}
在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法
private void writeNull() throws IOException {
// TC_NULL = (byte)0x70;
// 表示对一个Object引用的描述的结束
bout.writeByte(TC_NULL);
}
如果不为null,则一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
// TC_CLASSDESC = (byte)0x72;
// 表示一个新的Class描述符
bout.writeByte(TC_CLASSDESC);
handles.assign(unshared ? null : desc);
if (protocol == PROTOCOL_VERSION_1) {
// do not invoke class descriptor write hook with old protocol
desc.writeNonProxy(this);
} else {
writeClassDescriptor(desc);
}
Class cl = desc.forClass();
bout.setBlockDataMode(true);
if (cl != null && isCustomSubclass()) {
ReflectUtil.checkPackageAccess(cl);
}
annotateClass(cl);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
//如果有父类会再去写入父类的描述信息
writeClassDesc(desc.getSuperDesc(), false);
}
在这个方法中首先会写入一个字节的TC_CLASSDESC,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()方法写入实际的类元信息,writeNonProxy()实现如下:
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name); // 写入类的名字
out.writeLong(getSerialVersionUID()); // 写入类的序列号,当没有声明序列号的时候才会自动生成
byte flags = 0;
// 获取类的标识
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
int protocol = out.getProtocolVersion();
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
flags |= ObjectStreamConstants.SC_ENUM;
}
out.writeByte(flags); // 写入类的flag
out.writeShort(fields.length); // 写入对象的字段的个数
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
out.writeByte(f.getTypeCode());//写入字段的类型
out.writeUTF(f.getName());//写入字段的名字(先写名字所占的utf编码的长度,再写名字)
if (!f.isPrimitive()) {
// 如果不是基本类型,即是class、Interface、array
// 则会写入表示对象或者类的类型字符串
out.writeTypeString(f.getTypeString());
//f.getTypeString()返回jvm类型标记
}
}
}
如果被写入的字段不是基本类型,则会接着调用writeTypeString()方法写入代表对象或者类的类型字符串,该方法需要一个参数,表示对应的类或者接口的字符串,最终调用的还是writeString()方法,实现如下
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) {
// final static byte TC_STRING = (byte)0x74;
// 表示接下来的字节表示一个字符串
bout.writeByte(TC_STRING);
bout.writeUTF(str, utflen);
} else {
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}
在这个方法中会先写入一个标志位TC_STRING表示接下来的数据是一个字符串,接着会调用writeUTF()写入字符串。
执行完上面的过程之后,程序流程重新回到writeNonProxyDesc()方法中
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
// 其他省略代码
// TC_ENDBLOCKDATA = (byte)0x78;
// 表示对一个object的描述块的结束
bout.writeByte(TC_ENDBLOCKDATA);
writeClassDesc(desc.getSuperDesc(), false); // 递归调用,写入父类的类元数据
}
需要注意的是writeClassDesc()这个方法是个递归调用,调用结束返回的条件是没有父类,即传入的ObjectStreamClass对象为null,这个时候会写入一个字节的标识位TC_NULL,可以看ObjectStreamClass的构造方法。
在递归调用完成写入类的类元数据之后,程序执行流程回到wriyeOrdinaryObject()方法中,
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared) throws IOException
{
// 其他省略代码
try {
desc.checkSerialize();
// 其他省略代码
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc); // 写入被序列化的对象的实例数据
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
从上面的分析中我们可以知道,当写入类的元数据的时候,是先写子类的类元数据,然后递归调用的写入父类的类元数据。
接下来会调用writeSerialData()方法写入被序列化的对象的字段的数据,方法实现如下:
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
// 获取表示被序列化对象的数据的布局的ClassDataSlot数组,父类在前
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
// 如果被序列化对象自己实现了writeObject()方法,则执行if块里的代码
// 一些省略代码
} else {
// 调用默认的方法写入实例数据
defaultWriteFields(obj, slotDesc);
}
}
}
在这个方法中首先会调用getClassDataLayout()方法获取被序列化对象的数据的布局,关于这个方法官方文档中说明如下:
/**
* Returns array of ClassDataSlot instances representing the data layout
* (including superclass data) for serialized objects described by this
* class descriptor. ClassDataSlots are ordered by inheritance with those
* containing "higher" superclasses appearing first. The final
* ClassDataSlot contains a reference to this descriptor.
*/
ClassDataSlot[] getClassDataLayout() throws InvalidClassException;
需要注意的是这个方法会把从父类继承的数据一并返回,并且表示从父类继承的数据的ClassDataSlot对象在数组的最前面。
对于没有自定义writeObject()方法的对象来说,接下来会调用defaultWriteFields()方法写入数据,该方法实现如下:
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
// 其他一些省略代码
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
// 获取对应类中的基本数据类型的数据并保存在primVals字节数组中(底层通过Unsafe的原子操作完成)
desc.getPrimFieldValues(obj, primVals);
// 把基本数据类型的数据写入底层字节容器中
bout.write(primVals, 0, primDataSize, false);
// 获取对应类的所有的字段对象
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
// 把对应类的Object类型(非基本类型)的对象值保存到objVals数组中
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
// 一些省略的代码
try {
// 对所有Object类型的字段递归调用writeObject0()方法写入对应的数据
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
可以看到,在这个方法中会做下面几件事情:
<1> 获取对应类的基本类型的字段的数据,并写入到底层的字节容器中。
<2> 获取对应类的Object类型(非基本类型)的字段成员,递归调用writeObject0()方法写入相应的数据,因此也需要实现序列化接口。
从上面对写入数据的分析可以知道,写入数据是按照先父类后子类的顺序来写的。
反序列化
反序列化对象时需要调用ObjectInputStream的readObject()方法,
public final Object readObject()
throws IOException, ClassNotFoundException
{
...
Object obj = readObject0(false);
...
return obj;
...
}
主要调用readObject0()方法,源码如下:
private Object readObject0(boolean unshared) throws IOException {
...
depth++;
try {
//根据序列化时写入的标记头,选择对应的反序列化方式
switch (tc) {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
主要分析TC_OBJECT的情况,其余的也差不多,
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//重复检查标记是否为Object
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
//下面重点讲解
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
//获取描述对应的类
Class<?> cl = desc.forClass();
//排除String、Class、ObjectStreamClass这三个类,序列化时就做了特殊处理
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
//根据描述数据中的构造函数,利用反射创建对象,构造函数的规则在序列化时已经说明
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
...
//两种接口的不同实现
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
//实现Serializable接口的调用
readSerialData(obj, desc);
}
...
//判断是否存在readResolve()方法
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
//执行并返回替换的对象
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
下面看看readClassDesc()的源码:
private ObjectStreamClass readClassDesc(boolean unshared)
throws IOException
{
byte tc = bin.peekByte();
switch (tc) {
case TC_NULL:
return (ObjectStreamClass) readNull();
case TC_REFERENCE:
return (ObjectStreamClass) readHandle(unshared);
case TC_PROXYCLASSDESC:
return readProxyDesc(unshared);
case TC_CLASSDESC:
return readNonProxyDesc(unshared);
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
}
对应序列化时,null,reference,proxy和nonProxy的情况,这里主要分析非代理对象的反序列化。
private ObjectStreamClass readNonProxyDesc(boolean unshared)
throws IOException
{
//检查写入的是否为非代理对象
if (bin.readByte() != TC_CLASSDESC) {
throw new InternalError();
}
ObjectStreamClass desc = new ObjectStreamClass();
...
ObjectStreamClass readDesc = null;
try {
//获取序列化时保存的描述类元信息,按照序列化时的顺序读取
readDesc = readClassDescriptor();
...
Class cl = null;
...
final boolean checksRequired = isCustomSubclass();
try {
//初始化加载描述类代表的序列化类
if ((cl = resolveClass(readDesc)) == null)
...
skipCustomData();
//初始化描述类元信息,注意这里又递归调用readClassDesc。我们序列化的时候是先写入子类的类元信息,再写入父类的;反序列化时,也需要先读入子类再父类,因此readClassDesc返回的是父类的类元描述信息,但是具体的初始化类元信息顺序还是先初始化父类再子类。
desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
...
return desc;
}
回到readNonProxyDesc()方法,看看ObjectStreamClass的initNonProxy()方法,
void initNonProxy(ObjectStreamClass model,
Class<?> cl,
ClassNotFoundException resolveEx,
ObjectStreamClass superDesc)
throws InvalidClassException
{
//类元数据的各个字段赋值
...
if (cl != null) {
//当前程序中对应类的类元信息
localDesc = lookup(cl, true);
//各种字段验证
...
//如果当前程序中的序列号和反序列化出来的不相等就报异常
if (serializable == localDesc.serializable &&
!cl.isArray() &&
suid.longValue() != localDesc.getSerialVersionUID())
{
throw new InvalidClassException(localDesc.name,
"local class incompatible: " +
"stream classdesc serialVersionUID = " + suid +
", local class serialVersionUID = " +
localDesc.getSerialVersionUID());
}
...
//本地程序中描述的各个方法
cons = localDesc.cons;
writeObjectMethod = localDesc.writeObjectMethod;
readObjectMethod = localDesc.readObjectMethod;
readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
writeReplaceMethod = localDesc.writeReplaceMethod;
readResolveMethod = localDesc.readResolveMethod;
if (deserializeEx == null) {
deserializeEx = localDesc.deserializeEx;
}
}
fieldRefl = getReflector(fields, localDesc);
// reassign to matched fields so as to reflect local unshared settings
fields = fieldRefl.getFields();
}
回到主方法readOrdinaryObject(),当反序列化实现Serializable接口的对象时
private void readSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
//从父类开始
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slots[i].hasData) {
if (obj != null &&
slotDesc.hasReadObjectMethod() &&
handles.lookupException(passHandle) == null)
{
...
//如果有readObject()执行
slotDesc.invokeReadObject(obj, this);
...
} else {
//如果没有的话就执行默认的反序列化,与序列化类似
defaultReadFields(obj, slotDesc);
}
if (slotDesc.hasWriteObjectData()) {
skipCustomData();
} else {
bin.setBlockDataMode(false);
}
} else {
if (obj != null &&
slotDesc.hasReadObjectNoDataMethod() &&
handles.lookupException(passHandle) == null)
{
slotDesc.invokeReadObjectNoData(obj);
}
}
}
}
源码中的修饰符和对应的十六进制
修饰符 | 十六进制 |
---|---|
PUBLIC | 0x00000001 |
PRIVATE | 0x00000002 |
PROTECTED | 0x00000004 |
STATIC | 0x00000008 |
FINAL | 0x00000010 |
SYNCHRONIZED | 0x00000020 |
VOLATILE | 0x00000040 |
TRANSIENT | 0x00000080 |
NATIVE | 0x00000100 |
INTERFACE | 0x00000200 |
ABSTRACT | 0x00000400 |
STRICT | 0x00000800 |
BRIDGE | 0x00000040 |
VARARGS | 0x00000080 |
SYNTHETIC | 0x00001000 |
ANNOTATION | 0x00002000 |
ENUM | 0x00004000 |
总结概述
- 写入文件头(往底层字节容器中写入表示序列化的Magic Number以及版本号);
- 获取要序列化的对象的Class对象,并创建对应描述的ObjectStreamClass对象;
- 如果这个class的对应描述对象中有writeReplace()方法就执行并返回替换后需要序列化的对象,再继续序列化返回的对象。。。。。。;
- 如果是对象(除了String、Array、Enum)都要实现序列化接口才可以序列化;
- 往底层字节容器中写入TC_OBJECT,表示这是一个新的Object;
- 写入类元数据:
(当写入类的元数据的时候,是先写子类的类元数据,然后递归调用的写入父类的类元数据(只有实现序列化接口的才会有类元数据))
6.1. 如果类元数据为空,就写入TC_NULL,表示引用的描述结束;
6.2. 写入TC_CLASSDESC,表示一个新的Class描述符;
6.3. 写入实际的类元信息:写入类的名字,写入类的序列号,当没有声明序列号的时候才会自动生成,获取类的标识并写 入,写入对象的字段的个数,如果有字段的话,写入字段的类型,写入字段的名字(先写名字所占的utf编码的长度,再写名字),如果不是基本类型,即是class、Interface、array则会写入表示对象或者类的类型字符串;
6.4. 写入TC_ENDBLOCKDATA,表示对一个object的描述块的结束;
6.5. 递归调用,写入父类的类元数据。 - 看序列化对象实现的是哪个接口:1)如果实现Externalizable就直接执行它的writeExternal()方法;2)如果实现Serializable,先写父类的数据,再一层层下来;写的时候如果序列化对象实现了writeObject()方法(要实现序列化的类中有才有效果)就执行这个方法,如果没有实现,就执行默认的序列化方法。默认的序列化方法:获取对应类中的基本数据类型的数据并保存在primVals字节数组中(底层通过Unsafe的原子操作完成),把基本数据类型的数据写入底层字节容器中,把对应类的Object类型(非基本类型)的对象值保存到objVals数组中,对所有Object类型的字段递归调用writeObject0()方法(序列化对象方法)写入对应的数据。
ObjectStreamClass构造函数:
包含属性:类对象、类名、(代理、枚举、serializable、externalizable、父类的描述对象、当前类的描述对象)、
没有实现序列化接口:序列号为0,序列化字段为空;
实现序列化接口:
1.枚举的序列号为0和字段为空;
2.数组的字段为空;
3.通过反射拿到声明为static final修饰的serialVersionUID字段的值;
4.获取实现Serializable接口的对象的字段,(实现Externalizable的为空字段),先获取声明的private static final ObjectStreamField[] serialPersistentFields,它的规则是“声明指定要序列化的字段必须类型和名字要和实际的对应,且不能是static字段”,如果未声明就去获取默认的序列化字段(就是除了static和transient修饰的其他字段)。换句话说如果声明了这个字段,就不会去序列化类中的属性字段;
5.计算并设置序列化字段偏移量;
6.一个实现Externalizable接口的类要有一个公有的无参构造函数,主要在反序列化的时候需要;
7.一个类实现了Serializable但未实现Externalizable时,找到其父类中第一个未实现Serializable接口的类(Object类的父类为空)的无参构造方法,其规则是“无参构造函数不能是private的,可以是public和protected的,如果是默认的访问修饰符,那么一定需要和当前序列化对象在同一个包下”,因为反序列化时实例化子类必须先实例化其父类;
8.规定了3个方法的格式,方法是private的,返回类型为returnType,且是非static,
private void writeObject(ObjectOutputStream out)
private void readObject(ObjectInputStream in)
private void readObjectNoData();
9.规定writeReplace、readResolve方法的格式,从当前类开始一直递归查找父类中是否声明了这个方法(只包括方法名、方法参数类型、返回对象类型),直到查询到第一个为止,方法不能是static和abstract的,可以是public和protected修饰的,如果修饰符是private的,那这个方法必须在序列化对象的当前类中,如果采用默认修饰符,那么这个方法所在的类必须和序列化对象的当前类在同一个包下。
自定义序列化与反序列化
序列化的情况太多了,无法一一列举出来,但可以从源码中找到实现的规则,这里给出两个序列化的例子。
实现Serializable:
public class PersonSingleton implements Serializable {
//不写的话会自动生成
private static final long serialVersionUID = 1L;
//声明要序列化的字段,不写才会去序列化默认的字段
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[]{};
private String name;
private PersonSingleton(String name) {
this.name = name;
}
private static volatile PersonSingleton person = null;
public static PersonSingleton getInstance() {
if (person == null){
synchronized (PersonSingleton.class){
if(person == null)
return person = new PersonSingleton("ljh");
}
}
return person;
}
private Object writeReplace() throws ObjectStreamException {
System.out.println("1 write replace start");
return this;//可修改为其他对象,序列化这个对象,所以这个对象必须实现序列化
}
private void writeObject(ObjectOutputStream out) throws IOException {
System.out.println("2 write object start");
// out.defaultWriteObject();
//out.writeInt(1);
out.writeObject("hello");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("3 read object start");
// in.defaultReadObject();
name = (String) in.readObject();
}
//最主要应用场合就是单例、枚举类型的保护性恢复!
private Object readResolve() throws ObjectStreamException {
System.out.println("4 read resolve start");
return PersonSingleton.getInstance();//不管序列化的操作是什么,返回的都是本地的单例对象,结果作为readObject()的返回结果
}
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream(new File("G:/serializable.txt"));
ObjectOutputStream op = new ObjectOutputStream(out);
op.writeObject(PersonSingleton.getInstance());
op.close();
FileInputStream in = new FileInputStream(new File("G:/serializable.txt"));
ObjectInputStream oi = new ObjectInputStream(in);
Object person = oi.readObject();
in = new FileInputStream(new File("G:/serializable.txt"));
oi = new ObjectInputStream(in);
PersonSingleton person1 = (PersonSingleton) oi.readObject();
System.out.println("sington person hashcode:" + person.hashCode());
System.out.println("sington person1 hashcode:" + person1.hashCode());
System.out.println("singleton getInstance hashcode:" + PersonSingleton.getInstance().hashCode());
System.out.println("singleton person equals:" + (person == PersonSingleton.getInstance()));
System.out.println("person equals1:" + (person1 == person));
}
}
实现Externalizable:
public class External implements Externalizable {
//没有声明序列化的serialPersistentFields属性,因为被设成了NO_FIELDS
private String name;
private Integer age;
public External(String name, Integer age){
this.name = name;
this.age = age;
}
//一定要显示的声明一个公有的无参构造函数
public External() { }
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
//这两个方法是只要实现了序列化接口都可以声明并执行,但是注意,当源码中判断desc中是否有这个方法的时候,desc代表的这个类一定需要实现序列化接口,但是这个方法却可以声明在未实现序列化接口的父类中,不过要注意访问修饰符,意思就是desc代表的这个类能够调用到
private Object writeReplace() throws ObjectStreamException {
System.out.println("1 write replace start");
return this;//可修改为其他对象,序列化这个对象,所以这个对象必须实现序列化
}
private Object readResolve() throws ObjectStreamException {
System.out.println("4 read resolve start");
return this;
}
public static void main(String[] args) throws Exception {
External external = new External("ljh", 24);
FileOutputStream out = new FileOutputStream(new File("G:/serializable.txt"));
ObjectOutputStream op = new ObjectOutputStream(out);
op.writeObject(external);
op.close();
FileInputStream in = new FileInputStream(new File("G:/serializable.txt"));
ObjectInputStream oi = new ObjectInputStream(in);
External ex = (External) oi.readObject();
System.out.println(ex.name + " " + ex.age);
}
}
拓展
阅读完源码后可能会有个疑惑,就是反序列化中实例化对象的时候明明拿到的是父类中第一个non_serializable的类的无参构造函数,它是怎么拿到这个实例化对象的呢?
public class Child extends Super{
public Child(){
System.out.println("child");
}
private void c(){
System.out.println("c");
}
@Override
public void ss() {
System.out.println("cc");
}
public static void main(String[] args) throws Exception {
ReflectionFactory reflFactory =
AccessController.doPrivileged(
new ReflectionFactory.GetReflectionFactoryAction());
Constructor sup = Super.class.getDeclaredConstructor();
sup = reflFactory.newConstructorForSerialization(Child.class, sup);
Child child = (Child) sup.newInstance();
Method m = Super.class.getDeclaredMethod("s");
m.setAccessible(true);
m.invoke(child);
Method m2 = Super.class.getDeclaredMethod("ss");
m2.setAccessible(true);
m2.invoke(child);
}
}
class Super{
public Super(){
System.out.println("super");
}
private void s(){
System.out.println("s");
}
//被子类重写了,反射调用到的是子类实现的
public void ss(){
System.out.println("ss");
}
}
//output:
super
s
cc
调用反射工厂的方法并没有执行子类的构造函数,但的的确确生成了子类对象。