歡迎光臨
每天分享高質量文章

深入學習 Java 序列化

(點選上方公眾號,可快速關註)


來源:天涼好個秋,

beautyboss.farbox.com/post/study/shen-ru-xue-xi-javaxu-lie-hua

前言

對於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檔案中的二進位制分別代表什麼意思?

別急,且聽我娓娓道來。

1. 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()

在呼叫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 essay-header.

 */

final static short STREAM_MAGIC = (short)0xaced;

 

/**

 * Version number that is written to the stream essay-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);

    }

}

從程式碼裡面可以看到,程式會

  • 生成一個描述被序列化物件的類的類元資訊的ObjectStreamClass物件。

  • 根據傳入的需要序列化的物件的實際型別進行不同的序列化操作。從程式碼裡面可以很明顯的看到,對於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()方法中會按照以下幾個過程來寫入資料:

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

        }

    }

2. 接下來會呼叫writeLong()方法寫入類的序列號UID,UID是透過getSerialVersionUID()方法來獲取。

3. 接著會判斷被序列化的物件所屬類的flag,並寫入底層位元組容器中(佔用兩個位元組)。類的flag分為以下幾類:

  • final static byte SC_EXTERNALIZABLE = 0×04;表示該類為Externalizable類,即實現了Externalizable介面。

  • final static byte SC_SERIALIZABLE = 0×02;表示該類實現了Serializable介面。

  • final static byte SC_WRITE_METHOD = 0×01;表示該類實現了Serializable介面且自定義了writeObject()方法。

  • final static byte SC_ENUM = 0×10;表示該類是個Enum型別。

對於本例中flag = 0×02表示只是Serializable型別。

4. 第四步會依次寫入被序列化物件的欄位的元資料。

<1> 首先會寫入被序列化物件的欄位的個數,佔用兩個位元組。本例中為2,因為TestObject類中只有兩個欄位,一個是int型別的testValue,一個是InnerObject型別的innerValue。

<2> 依次寫入每個欄位的元資料。每個單獨的欄位由ObjectStreamField類來表示。

1) 寫入欄位的型別碼,佔一個位元組。 型別碼的對映關係如下:

2) 呼叫writeUTF()方法寫入每個欄位的名字。註意,writeUTF()方法會先寫入名字佔用的位元組數。

3) 如果被寫入的欄位不是基本型別,則會接著呼叫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();

            }

        }

    }

}

可以看到,在這個方法中會做下麵幾件事情:

<1> 獲取對應類的基本型別的欄位的資料,並寫入到底層的位元組容器中。

<2> 獲取對應類的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開始

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

3. 反序列化:readObject()

反序列化過程就是按照前面介紹的序列化演演算法來解析二進位制資料。

有一個需要註意的問題就是,如果子類實現了Serializable介面,但是父類沒有實現Serializable介面,這個時候進行反序列化會發生什麼情況?

答:如果父類有預設建構式的話,即使沒有實現Serializable介面也不會有問題,反序列化的時候會呼叫預設建構式進行初始化,否則的話反序列化的時候會丟擲.InvalidClassException:異常,異常原因為no valid constructor。

第四部分:Other

1. static和transient欄位不能被序列化。

序列化的時候所有的資料都是來自於ObejctStreamClass物件,在生成ObjectStreamClass的建構式中會呼叫fields = getSerialFields(cl);這句程式碼來獲取需要被序列化的欄位,getSerialFields()方法實際上是呼叫getDefaultSerialFields()方法的,getDefaultSerialFields()實現如下:

private static ObjectStreamField[] getDefaultSerialFields(Class > cl) {

    Field[] clFields = cl.getDeclaredFields();

    ArrayList 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修飾的欄位給過濾掉。

在進行反序列化的時候會給預設值。

2. 如何實現自定義序列化和反序列化?

只需要被序列化的物件所屬的類定義了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。

2. 在呼叫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()方法。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂