前言

对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化的底层实现,这篇文章算是这次的学习小结吧。

第一部分:What

Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。

那么为什么需要序列化呢

第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。

第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。

第二部分:How

本部分以序列化到文件为例讲解Java序列化的基本用法。

package com.beautyboss.slogen;
import java.io.*;
/*** Author : Slogen* AddTime : 17/4/30*/
public class SerializableTest {public static void main(String[] args) throws Exception {FileOutputStream fos = new FileOutputStream("temp.out");ObjectOutputStream oos = new ObjectOutputStream(fos);TestObject testObject = new TestObject();oos.writeObject(testObject);oos.flush();oos.close();FileInputStream fis = new FileInputStream("temp.out");ObjectInputStream ois = new ObjectInputStream(fis);TestObject deTest = (TestObject) ois.readObject();System.out.println(deTest.testValue);System.out.println(deTest.parentValue);System.out.println(deTest.innerObject.innerValue);}
}
class Parent implements Serializable {private static final long serialVersionUID = -4963266899668807475L;public int parentValue = 100;
}
class InnerObject implements Serializable {private static final long serialVersionUID = 5704957411985783570L;public int innerValue = 200;
}
class TestObject extends Parent implements Serializable {private static final long serialVersionUID = -3186721026267206914L;public int testValue = 300;public InnerObject innerObject = new InnerObject();
}

程序执行完用vim打开temp.out文件,可以看到

0000000: aced 0005 7372 0020 636f 6d2e 6265 6175  ....sr. com.beau
0000010: 7479 626f 7373 2e73 6c6f 6765 6e2e 5465  tyboss.slogen.Te
0000020: 7374 4f62 6a65 6374 d3c6 7e1c 4f13 2afe  stObject..~.O.*.
0000030: 0200 0249 0009 7465 7374 5661 6c75 654c  ...I..testValueL
0000040: 000b 696e 6e65 724f 626a 6563 7474 0023  ..innerObjectt.#
0000050: 4c63 6f6d 2f62 6561 7574 7962 6f73 732f  Lcom/beautyboss/
0000060: 736c 6f67 656e 2f49 6e6e 6572 4f62 6a65  slogen/InnerObje
0000070: 6374 3b78 7200 1c63 6f6d 2e62 6561 7574  ct;xr..com.beaut
0000080: 7962 6f73 732e 736c 6f67 656e 2e50 6172  yboss.slogen.Par
0000090: 656e 74bb 1eef 0d1f c950 cd02 0001 4900  ent......P....I.
00000a0: 0b70 6172 656e 7456 616c 7565 7870 0000  .parentValuexp..
00000b0: 0064 0000 012c 7372 0021 636f 6d2e 6265  .d...,sr.!com.be
00000c0: 6175 7479 626f 7373 2e73 6c6f 6765 6e2e  autyboss.slogen.
00000d0: 496e 6e65 724f 626a 6563 744f 2c14 8a40  InnerObjectO,..@
00000e0: 24fb 1202 0001 4900 0a69 6e6e 6572 5661  $.....I..innerVa
00000f0: 6c75 6578 7000 0000 c8                   luexp....

第三部分:Why

调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?temp.out文件中的二进制分别代表什么意思?

别急,且听我娓娓道来。

Ⅰ.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对象。

Ⅱ.序列化:writeObject()

在调用wroteObject()进行序列化之前会先调用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以及版本号,定义为

/*** Magic number that is written to the stream header.*/
final static short STREAM_MAGIC = (short)0xaced;/*** Version number that is written to the stream header.*/
final static short STREAM_VERSION = 5;

接下来会调用writeObject()方法进行序列化,实现如下:

public final void writeObject(Object obj) throws IOException {if (enableOverride) {writeObjectOverride(obj);return;}try {// 调用writeObject0()方法序列化writeObject0(obj, false);}catch (IOException ex) {if (depth == 0) {writeFatalException(ex);}throw ex;}
}

正常情况下会调用writeObject0()进行序列化操作,该方法实现如下:

private void writeObject0(Object obj, boolean unshared)throws IOException
{// 一些省略代码try {// 一些省略代码Object orig = obj;// 获取要序列化的对象的Class对象Class cl = obj.getClass();ObjectStreamClass desc;for (;;) {Class repCl;// 创建描述cl的ObjectStreamClass对象desc = ObjectStreamClass.lookup(cl, true);// 其他省略代码}// 一些省略代码// 根据实际的类型进行不同的写入操作// remaining casesif (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);} else {if (extendedDebugInfo) {throw new NotSerializableException(cl.getName() + "n" + debugInfoStack.toString());} else {throw new NotSerializableException(cl.getName());}}} finally {depth--;bout.setBlockDataMode(oldMode);}
}

从代码里面可以看到,程序会

  1. 生成一个描述被序列化对象的类的类元信息的ObjectStreamClass对象。
  2. 根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到,对于String类型、数组类型和Enum可以直接进行序列化。如果被序列化对象实现了Serializable对象,则会调用writeOrdinaryObject()方法进行序列化。

这里可以解释一个问题:Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。

答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化

因此,序列化过程接下来会执行到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);} 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为nullwriteNull();} 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 protocoldesc.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);// 写入类的flagout.writeshort(fields.length);// 写入对象的字段的个数for (int i = 0; i < fields.length; i++) {ObjectStreamField f = fields[i];out.writebyte(f.getTypeCode());out.writeUTF(f.getName());if (!f.isPrimitive()) {// 如果不是原始类型,即是对象或者Interface// 则会写入表示对象或者类的类型字符串out.writeTypeString(f.getTypeString());}}
}

writeNonProxy()方法中会按照以下几个过程来写入数据:

调用writeUTF()方法写入对象所属类的名字,对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下:

  1. 调用writeUTF()方法写入对象所属类的名字
    对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下:
void writeUTF(String s, long utflen) throws IOException {if (utflen > 0xFFFFL) {throw new UTFDataFormatException();}// 写入两个字节的s的长度writeShort((int) utflen);if (utflen == (long) s.length()) {writeBytes(s);} else {writeUTFBody(s);}}

  1. 接下来会调用writeLong()方法写入类的序列号UID,UID是通过getSerialVersionUID()方法来获取。
  2. 接着会判断被序列化的对象所属类的flag,并写入底层字节容器中(占用两个字节)。类的flag分为以下几类:
  • final static byte SC_EXTERNALIZABLE = 0x04;表示该类为Externalizable类,即实现了Externalizable接口。
  • final static byte SC_SERIALIZABLE = 0x02;表示该类实现了Serializable接口。
  • final static byte SC_WRITE_METHOD = 0x01;表示该类实现了Serializable接口且自定义了writeObject()方法。
  • final static byte SC_ENUM = 0x10;表示该类是个Enum类型。

对于本例中flag = 0x02表示只是Serializable类型。

  1. 第四步会依次写入被序列化对象的字段的元数据。
  • 首先会写入被序列化对象的字段的个数,占用两个字节。本例中为2,因为TestObject类中只有两个字段,一个是int类型的testValue,一个是InnerObject类型的innerValue。
  • 依次写入每个字段的元数据。每个单独的字段由ObjectStreamField类来表示。
    • 写入字段的类型码,占一个字节。 类型码的映射关系如下:
TypeCode| Java Type—|—
B | byte
C | char
D | double
F | float
I | int
J | long
L | class or interface
S | short
Z | boolean
[ | array

  • 调用writeUTF()方法写入每个字段的名字。注意,writeUTF()方法会先写入名字占用的字节数。
  • 如果被写入的字段不是基本类型,则会接着调用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); // 尾递归调用,写入父类的类元数据
}

接下来会写入一个字节的标志位TC_ENDBLOCKDATA表示对一个object的描述块的结束。

然后会调用writeClassDesc()方法,传入父类的ObjectStreamClass对象,写入父类的类元数据。

需要注意的是writeClassDesc()这个方法是个递归调用,调用结束返回的条件是没有了父类,即传入的ObjectStreamClass对象为null,这个时候会写入一个字节的标识位TC_NULL.

在递归调用完成写入类的类元数据之后,程序执行流程回到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);}}
}

在这个方法中首先会调用getClassDataSlot()方法获取被序列化对象的数据的布局,关于这个方法官方文档中说明如下:

/*** 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字节数组中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();}}}
}

可以看到,在这个方法中会做下面几件事情:

  • 获取对应类的基本类型的字段的数据,并写入到底层的字节容器中。
  • 获取对应类的Object类型(非基本类型)的字段成员,递归调用writeObject0()方法写入相应的数据。

从上面对写入数据的分析可以知道,写入数据是是按照先父类后子类的顺序来写的。

至此,Java序列化过程分析完毕,总结一下,在本例中序列化过程如下:

现在可以来分析下第二步中写入的temp.out文件的内容了。

aced        Stream Magic
0005        序列化版本号
73          标志位:TC_OBJECT,表示接下来是个新的Object
72          标志位:TC_CLASSDESC,表示接下来是对Class的描述
0020        类名的长度为32
636f 6d2e 6265 6175 7479 626f 7373 2e73 com.beautyboss.s
6c6f 6765 6e2e 5465 7374 4f62 6a65 6374 logen.TestObject
d3c6 7e1c 4f13 2afe 序列号
02          flag,可序列化
00 02       TestObject的字段的个数,为2
49          TypeCode,I,表示int类型
0009        字段名长度,占9个字节
7465 7374 5661 6c75 65      字段名:testValue
4c          TypeCode:L,表示是个Class或者Interface
000b        字段名长度,占11个字节
696e 6e65 724f 626a 6563 74 字段名:innerObject
74          标志位:TC_STRING,表示后面的数据是个字符串
0023        类名长度,占35个字节
4c63 6f6d 2f62 6561 7574 7962 6f73 732f  Lcom/beautyboss/
736c 6f67 656e 2f49 6e6e 6572 4f62 6a65  slogen/InnerObje
6374 3b                                  ct;
78          标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束

接下来是对父类,也就是Parent类的描述

72      标志位:TC_CLASSDESC,表示接下来是对Class的描述
00 1c       类名的长度,为28
636f 6d2e 6265 6175 7479 626f   com.beautybo
7373 2e73 6c6f 6765 6e2e 5061   ss.slogen.Pa
7265 6e74           rent
bb 1eef 0d1f c950 cd        序列号
02      flag,表示可序列化
0001        字段个数,1个
49      TypeCode,I,表示int类型
000b        字段名长度,占11个字节
7061 7265 6e74 5661 6c75 65     字段名:parentValue
78      标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束
70      标志位:TC_NULL,Null object reference.

接下来开始写入数据,从父类Parent开始

0000 0064 parentValue的值:100
0000 012c testValue的值:300

接下来是写入InnerObject的类元信息

73  标志位,TC_OBJECT:表示接下来是个新的Object
72  标志位,TC_CLASSDESC:表示接下来是对Class的描述
0021    类名的长度,为33
636f 6d2e 6265 6175 7479 626f 7373  com.beautyboss
2e73 6c6f 6765 6e2e 496e 6e65 724f .slogen.InnerO
626a 6563 74                bject
4f2c 148a 4024 fb12     序列号
02      flag,表示可序列化
0001    字段个数,1个
49    TypeCode,I,表示int类型
00 0a  字段名长度,10个字节
69 6e6e 6572 5661 6c75 65 innerValue
78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束
70 标志位:TC_NULL,Null object reference.
0000 00c8  innervalue的值:200

Ⅲ.反序列化:readObject()

反序列化过程就是按照前面介绍的序列化算法来解析二进制数据。

有一个需要注意的问题就是,如果子类实现了Serializable接口,但是父类没有实现Serializable接口,这个时候进行反序列化会发生什么情况?

答:如果父类有默认构造函数的话,即使没有实现Serializable接口也不会有问题,反序列化的时候会调用默认构造函数进行初始化,否则的话反序列化的时候会抛出.InvalidClassException:异常,异常原因为no valid constructor。

第四部分:Other

Ⅰ.static和transient字段不能被序列化。

序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);这句代码来获取需要被序列化的字段,getSerialFields()方法实际上是调用getDefaultSerialFields()方法的,getDefaultSerialFields()实现如下:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {Field[] clFields = cl.getDeclaredFields();ArrayList<ObjectStreamField> list = new ArrayList<>();int mask = Modifier.STATIC | Modifier.TRANSIENT;for (int i = 0; i < clFields.length; i++) {if ((clFields[i].getModifiers() & mask) == 0) {// 如果字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去list.add(new ObjectStreamField(clFields[i], false, true));}}int size = list.size();return (size == 0) ? NO_FIELDS :list.toArray(new ObjectStreamField[size]);
}

从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉

Ⅱ.在进行反序列化的时候会给默认值。

如何实现自定义序列化和反序列化?

只需要被序列化的对象所属的类定义了void writeObject(ObjectOutputStream oos)和void readObject(ObjectInputStream ois)方法即可,Java序列化和反序列化的时候会调用这两个方法,那么这个功能是怎么实现的呢?

  1. 在ObjectClassStream类的构造函数中有下面几行代码:
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);

getPrivateMethod()方法实现如下:

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;}
}

可以看到在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。

  1. 在调用writeSerialData()方法写入序列化数据的时候有
private void writeSerialData(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 (slotDesc.hasWriteObjectMethod()) {// 其他一些省略代码try {curContext = new SerialCallbackContext(obj, slotDesc);bout.setBlockDataMode(true);// 在这里调用用户自定义的方法slotDesc.invokeWriteObject(obj, this);bout.setBlockDataMode(false);bout.writeByte(TC_ENDBLOCKDATA);} finally {curContext.setUsed();curContext = oldContext;if (extendedDebugInfo) {debugInfoStack.pop();}}curPut = oldPut;} else {defaultWriteFields(obj, slotDesc);}}
}

首先会调用hasWriteObjectMethod()方法判断有没有自定义的writeObject(),代码如下

boolean hasWriteObjectMethod() {return (writeObjectMethod != null);
}

hasWriteObjectMethod()这个方法仅仅是判断writeObjectMethod是不是等于null,而上面说了,如果用户自定义了void writeObject(ObjectOutputStream oos)这么个方法,则writeObjectMethod不为null,在if()代码块中会调用slotDesc.invokeWriteObject(obj, this);方法,该方法中会调用用户自定义的writeObject()方法。

更多关于Java的技术和资讯可以关注我的专栏:

Java架构筑基​zhuanlan.zhihu.com

专栏免费给大家分享Java架构的学习资料和视频

java序列化_深入学习Java序列化相关推荐

  1. groovy 使用java类_深入学习java中的Groovy 和 Scala 类

    前言 Java 传承的是平台,而不是语言.有超过 200 种语言可以在 JVM 上运行,它们之中不可避免地会有一种语言最终将取代 Java 语言,成为编写 JVM 程序的最佳方式.本系列将探讨三种下一 ...

  2. java安装_在线学习Java编程的最佳方法

    java安装 1.简介 Java是使用最广泛的编程语言之一. 根据Github的最新报告,Java被列为仅次于JavaScript的第二大最常用的编程语言. 掌握Java的人有很多话题. 好消息是,您 ...

  3. java樱花_初步学习java专业课程

    初步学习java编程一. 终于触碰到专业课了(有点小兴奋) 计算机语言:人类与计算机交流的语言! 特点:简洁.准确. 计算机语言分为1.二进制语言2.汇编语言3.C/C++{Java之父   Jame ...

  4. 深入理解java枚举_深入学习java枚举的应用

    一.枚举和静态常量区别 讲到枚举我们首先思考,它和public static final String 修饰的常量有什么不同. 我举枚举的两个优点: 1. 保证了类型安全:调用者无法随意传一个 int ...

  5. java培训分享:学习java开发的优势是什么

    想要进入到互联网行业的小伙伴,经常比较纠结学那个学科比较好,目前java.web前端.Python等都是非常热门的行业,前景也是比较好的,选择java学科的人比较多,那么学习java开发的优势是什么呢 ...

  6. java培训分享:学习Java需要什么软件

    在参加java培训过程中学习java技术,需要用到很多辅助工具,这些辅助工具是具有多功能性和实用性的,从代码构建到bug压缩.学习这些工具可以帮助您提高代码的质量,并成为一个更高效的Java开发人员. ...

  7. java string 序列化_详解JAVA序列化

    享学课堂作者:逐梦々少年 转载请声明出处! 现在开发过程中经常遇到多个进程多个服务间需要交互,或者不同语言的服务之间需要交互,这个时候,我们一般选择使用固定的协议,将数据传输过去,但是在很多语言,比如 ...

  8. fegin调用为什么要序列化_全方位解析Java的序列化

    前言 相信大家日常开发中,经常看到Java对象"implements Serializable".那么,它到底有什么用呢?本文从以下几个角度来解析序列这一块知识点~ 什么是Java ...

  9. 什么是java序列化_什么是Java序列化?为什么序列化?序列化有哪些方式?

    先普及一下,计算机中无法识别一个基本单元[字节]来表示,必须经过"翻译"才能让计算机理解人类的语言,这个翻译过程就是[编码],通常所说的字符转换为字节. ?有I/O的地方机就会涉及 ...

最新文章

  1. 推荐一个你最喜欢的辅助办公软件,你会推荐什么?
  2. Leetcode 剑指 Offer 40. 最小的k个数 (每日一题 20210825)
  3. 豆瓣9.6分!这部BBC的纪录片太让人震撼!
  4. leetcode64. 最小路径和(dp)
  5. python 安卓app开发环境搭建_基于react-native的APP开发环境配置
  6. 求后序遍历(信息学奥赛一本通-T1339)
  7. 演练 获取所有电视频道 FullChannels.xml c# 1614256914
  8. ubuntu 14.04 16.04 安装caffe+cuda8.0+pycafee总结
  9. SAP License:BSEG与FAGLFLEXA (A)表数据不一致问题
  10. Trees on the level UVA - 122 (二叉树的层次遍历)
  11. 怎样快速更新已安装的软件?
  12. 安装SQL2000时弹出“以前的某个程序安装已在安装计算机上创建挂起的文件操作、、、”...
  13. linux的审计功能(audit)
  14. SpringBoot2 结合BeetlSQL开发
  15. 泪目!java项目源码免费下载
  16. 游戏中的心理学(一):认知失调有前提条件
  17. Android Studio应用Freeline光速编译APK
  18. source 还原数据库
  19. 马化腾教你做产品:改掉这七点,让产品自己说话
  20. 计算机专业应届生简历表格,计算机专业应届生个人简历模板

热门文章

  1. set python比较随机_Python的Set和List的性能比较 + 两者之间的转换
  2. 修改了xiaoxia的sogou代理脚本使之能在windows下bind 127.0.0.1
  3. python xml第三方库_Python-XML库
  4. 运营商屡出奇招,移动物联网卡实名制或能整顿行业乱象
  5. 工作这么多年,很多人竟然还搞不清Java日志体系!
  6. 软件工程招聘c语言笔试,嵌入式软件工程师笔试题
  7. 2017中国无人机公开赛 总决赛 CDR Final 竞赛规则
  8. 【中间件从青铜到王者】第一篇: memcache初相识
  9. sizebox模型下载_Beware of box model size (留心盒子模型大小)
  10. 综述:智能城市的车联网部署:应用、架构和挑战