深入學習Java序列化

前言

對於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 cases
        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);
        } 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爲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());
        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序列化過程分析完畢,總結一下,在本例中序列化過程如下:

深入學習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()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章