1. 程式人生 > >【JDK原始碼分析】01-物件序列化ObjectOutputStream

【JDK原始碼分析】01-物件序列化ObjectOutputStream

上一篇文章提到反序列化列舉類仍是單例,而普通類物件不是單例,下面我們先分析一下序列化過程,序列化就是呼叫ObjectOutStream的物件的writeObject方法。我們先看一下序列化涉及的比較重要的一個ObjectStreamClass,JDK中的描述是:類的序列化描述符。它包含類描述資訊,欄位的描述資訊和 serialVersionUID。可以使用 lookup 方法找到/建立在此 Java VM 中載入的具體類的 ObjectStreamClass。ObjectOutputStream在序列化一個物件的時候就把這個物件的類描述資訊序列化的,要想獲得一個可序列化類的描述資訊那我們就可以呼叫ObjectStreamClass的lookup方法:

public static ObjectStreamClass lookup(Class<?> cl) {
    return lookup(cl, false);
}
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
    if (!(all || Serializable.class.isAssignableFrom(cl))) {
        return null;
}
    processQueue(Caches.localDescsQueue, Caches.localDescs);//處理已被GC回收的Class物件所對應的ObjectStreamClass物件
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue); Reference<?> ref = Caches.localDescs.get(key);//先嚐試從快取中找到cl對應的ObjectStreamClass物件 Object entry = null; if (ref != null) { entry = ref.get();//如果存在則取出 } EntryFuture future = null; if (entry == null) {//如果不存在則建立一個Future作為後續生成ObjectStreamClass例項的容器 EntryFuture newEntry = new
EntryFuture(); Reference<?> newRef = new SoftReference<>(newEntry); do { if (ref != null) {//移除舊的不引用任何物件的Reference Caches.localDescs.remove(key, ref); } ref = Caches.localDescs.putIfAbsent(key, newRef);//新建cl對應的引用,此時引用的只是一個空Future if (ref != null) { entry = ref.get(); } } while (ref != null && entry == null); if (entry == null) { future = newEntry; } }     //取得快取中的直接返回 if (entry instanceof ObjectStreamClass) { // check common case first return (ObjectStreamClass) entry; } if (entry instanceof EntryFuture) { future = (EntryFuture) entry; if (future.getOwner() == Thread.currentThread()) { /* * Handle nested call situation described by 4803747: waiting * for future value to be set by a lookup() call further up the * stack will result in deadlock, so calculate and set the * future value here instead. */ entry = null; } else { entry = future.get(); } } if (entry == null) { try { entry = new ObjectStreamClass(cl);//這是重點下面著重分析一下 } catch (Throwable th) { entry = th; } if (future.set(entry)) { Caches.localDescs.put(key, new SoftReference<Object>(entry));//更新Future為ObjectStreamClass } else { // nested lookup call already set future entry = future.get(); } } if (entry instanceof ObjectStreamClass) { return (ObjectStreamClass) entry; } else if (entry instanceof RuntimeException) { throw (RuntimeException) entry; } else if (entry instanceof Error) { throw (Error) entry; } else { throw new InternalError("unexpected entry: " + entry); } }
private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
name = cl.getName();//類名
isProxy = Proxy.isProxyClass(cl);//是否是JDK動態代理產生的物件類
isEnum = Enum.class.isAssignableFrom(cl);//是否列舉累
serializable = Serializable.class.isAssignableFrom(cl);//時候實現了序列化介面
externalizable = Externalizable.class.isAssignableFrom(cl);//是否實現了Externalozable介面
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);//列舉類固定serialVersionUID為0
fields = NO_FIELDS;//列舉欄位設為空
                    return null;
}
                if (cl.isArray()) {
                    fields = NO_FIELDS;//陣列.class的類描述欄位設為空
                    return null;
}

                suid = getDeclaredSUID(cl);//若不是列舉和陣列讀取類的靜態final的長整型欄位serialVersionUID,若沒明確指定該欄位值則通過getSerialVersionUID()方法分配一個值
                try {
                    fields = getSerialFields(cl);//獲取類中定義的私有靜態final的ObjectStreamField陣列serialPersistentFields,它指定了需要序列化的欄位,如果沒有指定則預設使用非靜態瞬時的成員欄位
computeFieldOffsets();
} catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                        new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}

                if (externalizable) {
                    cons = getExternalizableConstructor(cl);
} else {
                    cons = getSerializableConstructor(cl);//獲取最近沒有實現序列化介面的祖先類的無參構造方法要是public或protected或與父類在一個package中,否則返回null
writeObjectMethod = getPrivateMethod(cl, "writeObject",//儲存類的private void writeObject(ObjectOutputStream os)方法
                        new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",//儲存類的private void readObject(ObjectInputStream is)方法
                        new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(  //儲存類的private void readObjectNoData()方法
                        cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
                domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(  //儲存非static abstract Object writeReplace()方法,可繼承父類的但不能是私有
                    cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(  //儲存非static abstract Object readResolve()方法
                    cl, "readResolve", null, Object.class);
                return null;
}
        });
} else {  //沒有實現Serializable介面的類serialVersionUID設為0且類描述欄位陣列置為空陣列
        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(ex);
}
    if (deserializeEx == null) {
        if (isEnum) {
            deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {//注意這個地方也很重要,如果沒有cons反序列化的時候會丟擲no valid constructor
            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");
}
    }
    initialized = true;
}

有一點需要注意的是deserializeEx,反序列化時會判斷不為空的話丟擲他描述的異常資訊。譬如沒有一個合適的午餐建構函式就會拋no valid constructor,所以一般都要提供一個public的午餐構造方法以便序列化,這個下篇文章會再次提及。

rpublic long getSerialVersionUID() {
    // REMIND: synchronize instead of relying on volatile?
if (suid == null) {
        suid = AccessController.doPrivileged(
            new PrivilegedAction<Long>() {
                public Long run() {
                    return computeDefaultSUID(cl);
}
            }
        );
}
    return suid.longValue();
}
若果類中沒有定義[ANY-ACCESS-MODIFIER static final long serialVersionUID]欄位,則通過以上方法根據Class物件相關資訊分配一個值。

下面是ObjectStreamField定義,一目瞭然。

public class ObjectStreamField
    implements Comparable<Object>
{

    /** field name */
private final String name;
/** canonical JVM signature of field type */
private final String signature;
/** field type (Object.class if unknown non-primitive type) */
private final Class<?> type;
/** whether or not to (de)serialize field values as unshared */
private final boolean unshared;
/** corresponding reflective field object, if any */
private final Field field;
/** offset of field value in enclosing field group */
private int offset = 0;

我們看一下ObjectOutpupStream構造方法

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();//此處首先往輸出流中寫入magic和版本號
bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
} else {
        debugInfoStack = null;
}
}
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);//
bout.writeShort(STREAM_VERSION);
}

在構造方法中首先呼叫writeStreanHeader()方法往輸出流中寫入magic表明這是一個jdk序列化檔案格式以及版本號。

之後我們就可以呼叫writeObject()方法寫入物件了,實際呼叫了writeObject0()方法

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {//預設false
        writeObjectOverride(obj);
        return;
}
    try {
        writeObject0(obj, false);//此方法實際呼叫writeObject0()方法
} catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
}
        throw ex;
}
}
然後來到writeObject0()方法
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    boolean oldMode = bout.setBlockDataMode(false);
depth++;
    try {
        // handle previously written and non-replaceable objects
int h;
        if ((obj = subs.lookup(obj)) == null) {
            writeNull();
            return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
            writeHandle(h);
            return;
} else if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
            return;
} else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
            return;
}
        // check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
        for (;;) {
            // REMIND: skip this check for strings/arrays?
Class<?> repCl;
desc = ObjectStreamClass.lookup(cl, true);//1⃣️.獲取累的描述資訊
            if (!desc.hasWriteReplaceMethod() ||
                (obj = desc.invokeWriteReplace(obj)) == null ||
                (repCl = obj.getClass()) == cl)
            {
                break;
}
            cl = repCl;
}
        if (enableReplace) {
            Object rep = replaceObject(obj);
            if (rep != obj && rep != null) {
                cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
            obj = rep;
}
        // if object replaced, run through original checks a second time
if (obj != orig) {
            subs.assign(orig, obj);
            if (obj == null) {
                writeNull();
                return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
} else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
} else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
}
        }
        // remaining cases
if (obj instanceof String) {
            writeString((String) obj, unshared);//2⃣️序列化字串
} else if (cl.isArray()) {
            writeArray(obj, desc, unshared);//3⃣️序列化陣列
} else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);//4⃣️序列化列舉
} else if (obj instanceof Serializable) {
            writeOrdinaryObject(obj, desc, unshared);//5⃣️序列化實現了Serializable介面的物件
} 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⃣️到註釋5⃣️分別是序列化不同資料型別,我們分析一下序列化列舉writeEnum()和序列化writeOrdinaryObject()。

private void writeEnum(Enum<?> en,
ObjectStreamClass desc,
                       boolean unshared)
    throws IOException
{
    bout.writeByte(TC_ENUM);//第五個位元組代表資料型別,這裡是列舉
ObjectStreamClass sdesc = desc.getSuperDesc();//父類的描述資訊
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);//首先寫入類的描述資訊,然後寫入父類的描述資訊
handles.assign(unshared ? null : en);//快取一下,不是重點
writeString(en.name(), false);//寫入列舉name屬性
}

上面程式碼比較重要的就是寫入類的描述資訊writeClassDesc()方法和寫入列舉值writeString()方法。

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
        writeHandle(handle);
} else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
} else {
        writeNonProxyDesc(desc, unshared);//列舉型別走這
}
}

然後呼叫writeNonProxyDesc()方法:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    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);//然後寫入父類的描述資訊
}
protected void writeClassDescriptor(ObjectStreamClass desc)
    throws IOException
{
    desc.writeNonProxy(this);
}
寫入類的描述資訊就是呼叫ObjectOutputStream的writeNonProxy()方法。
void writeNonProxy(ObjectOutputStream out) throws IOException {
    out.writeUTF(name);//寫入類名
out.writeLong(getSerialVersionUID());//上面分析過列舉為0
    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); //綜合標誌--實現了哪種序列化介面有writeObjectData()方法是列舉型別嗎
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()) {
            out.writeTypeString(f.getTypeString());
}
    }
}

所以綜上寫入一個列舉型別的順序為 magic(short)->version(short)->TC_ENUM(byte)->TC_CLASSDESC(byte)->className(String)->serialVersionUID(long)->flags(byte)->TC_ENDBLOCKDATA(byte)->父類Enum的類描述資訊->TC_STRING或TC_LONGSTRING(byte)->列舉名稱(String)。

寫一個測試類:

public enum Gender {
    MAN,WOMAN;
}
private static void testSeriableEnum() throws IOException {
    ByteArrayOutputStream baos=new ByteArrayOutputStream();
    ObjectOutputStream oos=new ObjectOutputStream(baos);
    oos.writeObject(Gender.MAN);
    System.out.println(baos.toString());
}

結果如下:


以上就是列舉序列化的全部,列舉的序列化還是清晰明瞭的,先列舉本身然後列舉父類最後列舉名稱。

物件的序列化與列舉最大的區別在於欄位的序列化,我們看一下序列物件呼叫的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);
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();
}
    }
}

與列舉一樣先寫本類的描述資訊,然後一直沿著最先類寫到第一個沒有實現序列化介面的祖先類(不含)描述資訊,不同的是在writeNonProxy()方法中列舉沒有成員欄位可寫因為fields為長度為0的空陣列,而一般類則會依次寫入欄位類的描述資訊,先基本資料型別後物件型別。之後掉用writeSerialData()方法,在這個方法中如果該類沒有實現前面分析ObjectStreamClass物件儲存的writeObjet()方法則會呼叫defaultWriteFields()方法寫入該物件成員變亮的值。

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
}
    desc.checkDefaultSerialize();    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
}
    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;
desc.getObjFieldValues(obj, objVals);
    for (int i = 0; i < objVals.length; i++) {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                "field (class \"" + desc.getName() + "\", name: \"" +
                fields[numPrimFields + i].getName() + "\", type: \"" +
                fields[numPrimFields + i].getType() + "\")");
}
        try {//成員變數是一般類型別再次呼叫writeObject0()方法序列化此成員變數的物件,一個遞迴過程。
            writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
}
        }
    }
}

序列化物件欄位的過程就是如果該欄位是基本資料型別直接序列化,物件型別將該成員變數引用的物件再次當作一個可序列化物件傳入writeObject0方法的引數中是一個遞迴的過程。綜上,一般類的序列化過程就是首先將該類的描述資訊序列化,然後將其中所有可序列化的成員變數遞迴的方式序列化(成員變數是基本資料型別直接序列化,一般類型別遞迴序列化)。

寫個例子測試一下:

public class Human{
    public Human(String a){}
}
public class Person extends Human implements Serializable{
    public String xyz="lmn";
    public String name;
    public int age=55;
    public int length=66;
    public int width=77;

    public Person(String name) {
        super(name);
        this.name=name;
    }

}
private static void testSeriableEnum() throws IOException {
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);
//        oos.writeObject(Gender.MAN);
        oos.writeObject(new Person("abc"));
        System.out.println(baos.toString());
    }

執行結果:


注意因為Human沒有Serializable介面,所以序列化結果不包含Human的相關資訊。