1. Why serialization?

  • Store data locally
  • We know that data can only be transferred over the network in binary form, so JavaBean objects also need to be serialized when they are transferred over the network
  • IPC communication (data needs to be transferred through serialization because of process memory isolation)

2. What are serialization and deserialization

  • Serialization: The process of converting Java objects into byte sequences.
  • Deserialization: The process of restoring a sequence of bytes to Java objects.

3. Underlying principles of serialization and deserialization

Before we start our analysis, we need to consider a few issues with Java serialization:

  1. Why should Serializable be implemented?
  2. Why should serialization override serialVersionUID and what does it do?
  3. Why are transient and static fields filtered during serialization?

1. Serialization process

Serialization API calls

Person p = new Person();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File(localFile)));
outputStream.writeObject(p);// Write to the file
Copy the code

Initialize the constructor

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;// Turn off the override capability
    writeStreamHeader();// Write the serialization header
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null; }}Copy the code

Take a look at the writeStreamHeader() method, which is just writing some basic header identifier.

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
Copy the code

Go to the writeObject() method.

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {// Set false during initialization
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);// This method is called directly
    } catch (IOException ex) {
        if (depth == 0) {
            // BEGIN Android-changed: Ignore secondary exceptions during writeObject().
            // writeFatalException(ex);
            try {
                writeFatalException(ex);

            } catch (IOException ex2) {
                // If writing the exception to the output stream causes another exception there
                // is no need to propagate the second exception or generate a third exception,
                // both of which might obscure details of the root cause.
            }
            // END Android-changed: Ignore secondary exceptions during writeObject().
        }
        throwex; }}Copy the code

Go to the writeObject0() method.

  /** * Underlying writeObject/writeUnshared implementation. */
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try{... The code is omitted// check for replacement objectObject orig = obj; Class<? > cl = obj.getClass(); ObjectStreamClass desc;// Focus on this class. Code omitted Class repCl; desc = ObjectStreamClass.lookup(cl,true);// The desc object is initialized
            if(desc.hasWriteReplaceMethod() && (obj = desc.invokeWriteReplace(obj)) ! =null&& (repCl = obj.getClass()) ! = cl) { cl = repCl; desc = ObjectStreamClass.lookup(cl,true);
            }
            // END Android-changed: Make only one call to writeReplace.

            if (enableReplace) {
                Object rep = replaceObject(obj);
                if(rep ! = obj && rep ! =null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true); } obj = rep; }... The code is omitted// We can see that the following types are writable
            if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
            } else if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceofEnum) { writeEnum((Enum<? >) obj, desc, unshared); }else if (obj instanceof Serializable) {
            // Implementing Serializable executes the following method
            // Why should Serializable be implemented? That's the answer, Serializable is just a tag interface and doesn't mean anything by itself.
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else { // So if the Serializable interface is not implemented, an error is reported
                    throw newNotSerializableException(cl.getName()); }}}finally{ depth--; bout.setBlockDataMode(oldMode); }}Copy the code

Two important things are done in the writeObject0() method

  1. The ObjectStreamClass object is initialized with objectStreamClass.lookup (CL, true)
  2. Different operations are performed according to different write types

First, we’ll go to objectStreamClass.lookup (CL, true).

static ObjectStreamClass  lookup(Class<? > cl,boolean all) {
    if(! (all || Serializable.class.isAssignableFrom(cl))) {return null;
    }
    processQueue(Caches.localDescsQueue, Caches.localDescs);
    WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
    // Get the object from the cacheReference<? > ref = Caches.localDescs.get(key); Object entry =null;
    if(ref ! =null) {
        entry = ref.get();
    }
    EntryFuture future = null;
    if (entry == null) {
        EntryFuture newEntry = newEntryFuture(); Reference<? > newRef =new SoftReference<>(newEntry);
        do {
            if(ref ! =null) {
                Caches.localDescs.remove(key, ref);
            }
            ref = Caches.localDescs.putIfAbsent(key, newRef);
            if(ref ! =null) { entry = ref.get(); }}while(ref ! =null && entry == null);
        if (entry == null) { future = newEntry; }}// If it exists in the cache, return it directly
    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 {
            // Cache fetch failed, create object directly
            entry = new ObjectStreamClass(cl);
        } catch (Throwable th) {
            entry = th;
        }
        if (future.set(entry)) {
            Caches.localDescs.put(key, new SoftReference<Object>(entry));// Cache
        } else {
            // nested lookup call already set futureentry = 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); }}Copy the code

Enter the ObjectStreamClass constructor.

private ObjectStreamClass(finalClass<? > cl) {
    this.cl = cl;
    // Get some basic information about the classname = 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();// Get the parent typesuperDesc = (superCl ! =null)? lookup(superCl,false) : null;// The parent class is present, and the lookup method is called, recursing up until the parent class is null.
    localDesc = this;

    if (serializable) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run(a) {
                if (isEnum) {
                    suid = Long.valueOf(0);
                    fields = NO_FIELDS;
                    return null;
                }
                if (cl.isArray()) {
                    fields = NO_FIELDS;
                    return null;
                }

                suid = getDeclaredSUID(cl);// The serialVersionUID will be null if the serialized class is not declared.

                try {
                    fields = getSerialFields(cl);// Most importantly, get serialized attributes
                    computeFieldOffsets();
                } catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                        new ExceptionInfo(e.classname, e.getMessage());
                    fields = NO_FIELDS;
                }

                if (externalizable) {
                    cons = getExternalizableConstructor(cl);
                } else {
                    cons = getSerializableConstructor(cl);
                    // If the serialized class overrides the writeObject() and readObject() methods.
                    writeObjectMethod = getPrivateMethod(cl, "writeObject".newClass<? >[] { ObjectOutputStream.class }, Void.TYPE); readObjectMethod = getPrivateMethod(cl,"readObject".newClass<? >[] { ObjectInputStream.class }, Void.TYPE); readObjectNoDataMethod = getPrivateMethod( cl,"readObjectNoData".null, Void.TYPE); hasWriteObjectData = (writeObjectMethod ! =null);
                }
                writeReplaceMethod = getInheritableMethod(
                    cl, "writeReplace".null, Object.class);
                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(ex);
    }

    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");
        }
    }
    initialized = true;
}
Copy the code

Next, enter the getSerialFields() method.

private staticObjectStreamField[] getSerialFields(Class<? > cl)throws InvalidClassException
{
    ObjectStreamField[] fields;
    if(Serializable.class.isAssignableFrom(cl) && ! Externalizable.class.isAssignableFrom(cl) && ! Proxy.isProxyClass(cl) && ! cl.isInterface()) {if ((fields = getDeclaredSerialFields(cl)) == null) {
            fields = getDefaultSerialFields(cl);// call here again
        }
        Arrays.sort(fields);// Sort in ascending order
    } else {
        fields = NO_FIELDS;
    }
    return fields;
}
Copy the code

Next, go to the getDefaultSerialFields() method.

private staticObjectStreamField[] getDefaultSerialFields(Class<? > cl) { Field[] clFields = cl.getDeclaredFields(); ArrayList<ObjectStreamField> list =new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;
    // see STATIC and TRANSIENT keywords
    for (int i = 0; i < clFields.length; i++) {
            if ((clFields[i].getModifiers() & mask) == 0) {
            // With this line of code, serialized properties that are STATIC and TRANSIENT are filtered out, which is the answer to the third question!
            list.add(new ObjectStreamField(clFields[i], false.true)); }}int size = list.size();
    return (size == 0)? NO_FIELDS : list.toArray(new ObjectStreamField[size]);
}
Copy the code

The main work related to ObjectStreamClass is basically over. The collection of basic information, serialVersionUID, and properties of the serialized object is completed during initialization. And of course all the superclasses up there, it’s a recursive process.

Let’s go back to writing Serializable data in the writeObject0() method, writeOrdinaryObject(obj, desc, unshared).

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);// Write the Object identifier
        writeClassDesc(desc, false);// Write some class information
        handles.assign(unshared ? null : obj);
        if(desc.isExternalizable() && ! desc.isProxy()) { writeExternalData((Externalizable) obj); }else {
            writeSerialData(obj, desc);// Continue calling this method}}finally {
        if(extendedDebugInfo) { debugInfoStack.pop(); }}}Copy the code

The writeOrdinaryObject() method focuses on calling two methods, writeClassDesc() and writeSerialData(); Let’s look at writeClassDesc() first.

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        // If the object is null, write the NULL identifier
        writeNull();
    } else if(! unshared && (handle = handles.lookup(desc)) ! = -1) {
        writeHandle(handle);
    } else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
    } else {
        writeNonProxyDesc(desc, unshared);// This method is usually called}}Copy the code

Enter the writeNonProxyDesc() method.

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    bout.writeByte(TC_CLASSDESC);// Write the class meta information flag bit
    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);// Write the class descriptor} 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);// Call writeClassDesc(), pass in the parent class to initialize ObjectStreamClass, enter recursive mode, write the class descriptor information.
}
Copy the code

Go to the writeClassDescriptor() method.

protected void writeClassDescriptor(ObjectStreamClass desc)
    throws IOException
{
    desc.writeNonProxy(this);
}
Copy the code

Again, the writeNonProxy() method of the ObjectStreamClass object itself is called.

void writeNonProxy(ObjectOutputStream out) throws IOException {
    out.writeUTF(name);// Write the class name
    out.writeLong(getSerialVersionUID());// serialVersionUID appears again, where it is written

    
    // Determine the identification of the class type
    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;
    }
    // Write the class type identifier
    out.writeByte(flags);
    // Write information for serialized attributes
    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()); }}}Copy the code

In the writeNonProxy() method, a lot of information is written, so we also need to look at the getSerialVersionUID() method.

public long getSerialVersionUID(a) {
    // REMIND: synchronize instead of relying on volatile?
    if (suid == null) {// SuID is null if SerialVersionUID is not defined for the serialized class when ObjectStreamClass is initialized.
        suid = AccessController.doPrivileged(
            new PrivilegedAction<Long>() {
                public Long run(a) {
                    // Get the default value, so this is part of the answer to the second question, why define it in a sequence class
                    //SerialVersionUID, because if it is not defined it will be produced by default, and this default value is likely to change as the class content changes. Will produce
                    // What are the specific effects?
                    returncomputeDefaultSUID(cl); }}); }return suid.longValue();
}
Copy the code

After analyzing the writeClassDesc() process, we then analyze the writeSerialData() method to write real data.

private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();// Get an array of ClassDataSlot instances of the data layout
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        // If the serialized object implements its own writeObject() method, go to if.
        if (slotDesc.hasWriteObjectMethod()) {
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;

            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "custom writeObject data (class "" + slotDesc.getName() + "")");
            }
            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);// It is usually called here}}}Copy the code

Let’s first look at how to get an array of ClassDataSlot instances of the data layout.

ClassDataSlot[] getClassDataLayout() throws InvalidClassException {
    // REMIND: synchronize instead of relying on volatile?
    if (dataLayout == null) {
        dataLayout = getClassDataLayout0();
    }
    return dataLayout;
}
Copy the code

The getClassDataLayout0() method is called again.

private ClassDataSlot[] getClassDataLayout0()
    throws InvalidClassException
{
    ArrayList<ClassDataSlot> slots = newArrayList<>(); Class<? > start = cl, end = cl;// locate closest non-serializable superclass
    while(end ! =null && Serializable.class.isAssignableFrom(end)) {
        end = end.getSuperclass();
    }

    HashSet<String> oscNames = new HashSet<>(3);

    for (ObjectStreamClass d = this; d ! =null; d = d.superDesc) {
        if (oscNames.contains(d.name)) {
            throw new InvalidClassException("Circular reference.");
        } else {
            oscNames.add(d.name);
        }

        // search up inheritance hierarchy for class with matching nameString searchName = (d.cl ! =null) ? d.cl.getName() : d.name;
        Class<?> match = null;
        for(Class<? > c = start; c ! = end; c = c.getSuperclass()) {if (searchName.equals(c.getName())) {
                match = c;
                break; }}// Wrap all ObjectStreamClass objects as ClassDataSlot objects by iterating
        // add "no data" slot for each unmatched class below match
        if(match ! =null) {
            for(Class<? > c = start; c ! = match; c = c.getSuperclass()) { slots.add(new ClassDataSlot(
                    ObjectStreamClass.lookup(c, true), false));
            }
            start = match.getSuperclass();
        }

        // record descriptor/class pairing
        slots.add(new ClassDataSlot(d.getVariantFor(match), true));
    }

    // add "no data" slot for any leftover unmatched classes
    for(Class<? > c = start; c ! = end; c = c.getSuperclass()) { slots.add(new ClassDataSlot(
            ObjectStreamClass.lookup(c, true), false));
    }

    // order slots from superclass -> subclass
    Collections.reverse(slots);// Reverse is called, so the topmost parent ClassDataSlot object comes first.
    return slots.toArray(new ClassDataSlot[slots.size()]);
}
Copy the code

All ObjectStremClass objects are repackaged as ClassDataSlot objects and arranged in reverse order. Back in the writeSerialData() method, continue into 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];
    }
    // Get the primitive datatype instance data and store it in primVals
    desc.getPrimFieldValues(obj, primVals);
    // Write the data in the primVals array, so the basic data types are written directly here.
    bout.write(primVals, 0, primDataSize, false);

    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    // Get the unwritten object data into the objVals array
    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 {
            // call the writeObject0 method again to determine the type and perform a different write processing; It is also a recursive process until all data has been written
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if(extendedDebugInfo) { debugInfoStack.pop(); }}}}Copy the code

At this point, the serialization process is complete.

2. Deserialization process

Deserialize API calls

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(localFile)));
Person person = (Person) inputStream.readObject();// Read the file
Copy the code

Initialize the constructor

public ObjectInputStream(InputStream in) throws IOException { verifySubclass(); bin = new BlockDataInputStream(in); handles = new HandleTable(10); vlist = new ValidationList(); // Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929 // serialFilter = ObjectInputFilter.Config.getSerialFilter(); enableOverride = false; ReadStreamHeader () is also disabled; // Read the serialization header bin.setBlockDatamode (true); }Copy the code

Let’s look at the readStreamHeader() method, which reads the header information for damage determination.

protected void readStreamHeader() throws IOException, StreamCorruptedException { short s0 = bin.readShort(); short s1 = bin.readShort(); if (s0 ! = STREAM_MAGIC || s1 ! = STREAM_VERSION) { throw new StreamCorruptedException( String.format("invalid stream header: %04X%04X", s0, s1)); }}Copy the code

Let’s move on to the key readObject() method.

public final Object readObject(a)
    throws IOException, ClassNotFoundException
{
    if (enableOverride) {// Initialization is set to false and will not go.
        return readObjectOverride();
    }

    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);// This is then called here
        handles.markDependency(outerHandle, passHandle);
        ClassNotFoundException ex = handles.lookupException(passHandle);
        if(ex ! =null) {
            throw ex;
        }
        if (depth == 0) {
            vlist.doCallbacks();
        }
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) { clear(); }}}Copy the code

Enter the readObject0() method.

private Object readObject0(boolean unshared) throws IOException {
    boolean oldMode = bin.getBlockDataMode();
    if (oldMode) {
        int remain = bin.currentBlockRemaining();
        if (remain > 0) {
            throw new OptionalDataException(remain);
        } else if (defaultDataEnd) {
            /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */
            throw new OptionalDataException(true);
        }
        bin.setBlockDataMode(false);
    }

    byte tc;// Read the data type identifier
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }

    depth++;
    // Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929
    // totalObjectRefs++;
    try {
        switch (tc) {
            case TC_NULL:
                return readNull();

            case TC_REFERENCE:
                return readHandle(unshared);

            case TC_CLASS:
                return readClass(unshared);

            case TC_CLASSDESC:
            case TC_PROXYCLASSDESC:
                return readClassDesc(unshared);

            case TC_STRING:
            case TC_LONGSTRING:
                return checkResolve(readString(unshared));

            case TC_ARRAY:
                return checkResolve(readArray(unshared));

            case TC_ENUM:
                return checkResolve(readEnum(unshared));

            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));// Enter here

            case TC_EXCEPTION:
                IOException ex = readFatalException();
                throw new WriteAbortedException("writing aborted", ex);

            case TC_BLOCKDATA:
            case TC_BLOCKDATALONG:
                if (oldMode) {
                    bin.setBlockDataMode(true);
                    bin.peek();             // force header read
                    throw new OptionalDataException(
                        bin.currentBlockRemaining());
                } else {
                    throw new StreamCorruptedException(
                        "unexpected block data");
                }

            case TC_ENDBLOCKDATA:
                if (oldMode) {
                    throw new OptionalDataException(true);
                } else {
                    throw new StreamCorruptedException(
                        "unexpected end of block data");
                }

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc)); }}finally{ depth--; bin.setBlockDataMode(oldMode); }}Copy the code

We go to the readOrdinaryObject() method.

private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if(bin.readByte() ! = TC_OBJECT) {throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);// Read the description of the classdesc.checkDeserialize(); Class<? > cl = desc.forClass();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);
    }

    passHandle = handles.assign(unshared ? unsharedMarker : obj);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if(resolveEx ! =null) {
        handles.markException(passHandle, resolveEx);
    }

    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc);// Read serialized data
    }

    handles.finish(passHandle);

    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) {// Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929
            /* // Filter the replacement object if (rep ! = null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); }} * /handles.setObject(passHandle, obj = rep); }}return obj;
}
Copy the code

Let’s look at the readClassDesc() method first.

private ObjectStreamClass readClassDesc(boolean unshared)
    throws IOException
{
    byte tc = bin.peekByte();// Get the class meta information flag bit
    ObjectStreamClass descriptor;
    switch (tc) {
        case TC_NULL:
            descriptor = (ObjectStreamClass) readNull();
            break;
        case TC_REFERENCE:
            descriptor = (ObjectStreamClass) readHandle(unshared);
            break;
        case TC_PROXYCLASSDESC:
            descriptor = readProxyDesc(unshared);
            break;
        case TC_CLASSDESC:
            descriptor = readNonProxyDesc(unshared);// will be called here
            break;
        default:
            throw new StreamCorruptedException(
                String.format("invalid type code: %02X", tc));
    }
    // Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929
    // if (descriptor ! = null) {
    // validateDescriptor(descriptor);
    // }
    return descriptor;
}
Copy the code

Go to the readNonProxyDesc() method.

private ObjectStreamClass readNonProxyDesc(boolean unshared)
    throws IOException
{
    if(bin.readByte() ! = TC_CLASSDESC) {throw new InternalError();
    }

    ObjectStreamClass desc = new ObjectStreamClass();
    int descHandle = handles.assign(unshared ? unsharedMarker : desc);
    passHandle = NULL_HANDLE;

    ObjectStreamClass readDesc = null;
    try {
        readDesc = readClassDescriptor();// Read the description of the stored serialized class
    } catch (ClassNotFoundException ex) {
        throw (IOException) new InvalidClassException(
            "failed to read class descriptor").initCause(ex); } Class<? > cl =null;
    ClassNotFoundException resolveEx = null;
    bin.setBlockDataMode(true);
    final boolean checksRequired = isCustomSubclass();
    try {
        if ((cl = resolveClass(readDesc)) == null) {
            resolveEx = new ClassNotFoundException("null class");
        } else if(checksRequired) { ReflectUtil.checkPackageAccess(cl); }}catch (ClassNotFoundException ex) {
        resolveEx = ex;
    }
    skipCustomData();

    desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));// Initialize the class descriptor

    // Android-removed: ObjectInputFilter unsupported - removed filterCheck() call.
    // // Call filterCheck on the definition
    // filterCheck(desc.forClass(), -1);

    handles.finish(descHandle);
    passHandle = descHandle;

    return desc;
}
Copy the code

The readNonProxyDesc() method does two very critical things

  1. Read the description of the serialized class stored by calling the readClassDescriptor() method, and initialize the ObjectStreamClass object, assigning the readDesc attribute;
  2. This is initialized by calling the initNonProxy() method of the newly created ObjectStreamClass object Desc;

So just to make a distinction here, the readDesc local property object is created by the readClassDescriptor() method, whereas the desc property object is newly created and initialized by the initNonProxy() method, So let’s look at the readClassDescriptor() method first.

protected ObjectStreamClass readClassDescriptor(a)
    throws IOException, ClassNotFoundException
{
    ObjectStreamClass desc = new ObjectStreamClass();
    desc.readNonProxy(this);
    return desc;
}
Copy the code

An ObjectStreamClass object is created and the readNonProxy() method is called directly.

void readNonProxy(ObjectInputStream in)
    throws IOException, ClassNotFoundException
{
    name = in.readUTF();/ / the name of the class
    suid = Long.valueOf(in.readLong());// Familiar, serialVersionUID is back
    isProxy = false;

    byteflags = in.readByte(); hasWriteObjectData = ((flags & ObjectStreamConstants.SC_WRITE_METHOD) ! =0); hasBlockExternalData = ((flags & ObjectStreamConstants.SC_BLOCK_DATA) ! =0); externalizable = ((flags & ObjectStreamConstants.SC_EXTERNALIZABLE) ! =0);
    booleansflag = ((flags & ObjectStreamConstants.SC_SERIALIZABLE) ! =0);
    if (externalizable && sflag) {
        throw new InvalidClassException(
            name, "serializable and externalizable flags conflict"); } serializable = externalizable || sflag; isEnum = ((flags & ObjectStreamConstants.SC_ENUM) ! =0);
    if(isEnum && suid.longValue() ! =0L) {
        throw new InvalidClassException(name,
            "enum descriptor has non-zero serialVersionUID: " + suid);
    }

    int numFields = in.readShort();
    if(isEnum && numFields ! =0) {
        throw new InvalidClassException(name,
            "enum descriptor has non-zero field count: " + numFields);
    }
    fields = (numFields > 0)?new ObjectStreamField[numFields] : NO_FIELDS;
    for (int i = 0; i < numFields; i++) {
        // Loop to read serialized property information
        char tcode = (char) in.readByte();
        String fname = in.readUTF();
        String signature = ((tcode == 'L') || (tcode == '['))? in.readTypeString() :new String(new char[] { tcode });
        try {
            fields[i] = new ObjectStreamField(fname, signature, false);
        } catch (RuntimeException e) {
            throw (IOException) new InvalidClassException(name,
                "invalid descriptor for field " + fname).initCause(e);
        }
    }
    computeFieldOffsets();
}
Copy the code

With this analysis done, let’s go back to the newly created ObjectStreamClass object calling the initNonProxy() method.

void initNonProxy(ObjectStreamClass model, Class
        cl, ClassNotFoundException resolveEx, ObjectStreamClass superDesc)
    throws InvalidClassException
{
    // It is important to note that the model argument is readDesc and cl is the serialized class passed in
    long suid = Long.valueOf(model.getSerialVersionUID());// Get serialVersionUID()
    ObjectStreamClass osc = null;
    if(cl ! =null) {
        osc = lookup(cl, true);
        if (osc.isProxy) {
            throw new InvalidClassException(
                    "cannot bind non-proxy descriptor to a proxy class");
        }
        if(model.isEnum ! = osc.isEnum) {throw new InvalidClassException(model.isEnum ?
                    "cannot bind enum descriptor to a non-enum class" :
                    "cannot bind non-enum descriptor to an enum class");
        }

        if(model.serializable == osc.serializable && ! cl.isArray() && suid ! = osc.getSerialVersionUID()) {// key, key, key......
                // This is the answer to the second question. SerialVersionUID is used to cache objects by serializing them
                //serialVersionUID is compared to the serialVersionUID of the passed serialized class to see if the version is consistent
                // This is why it is recommended that you define your own serialVersionUID, so as to avoid version conflicts caused by changes in the serialVersionUID caused by class changes.
            throw new InvalidClassException(osc.name,
                    "local class incompatible: " +
                            "stream classdesc serialVersionUID = " + suid +
                            ", local class serialVersionUID = " +
                            osc.getSerialVersionUID());
        }

        if(! classNamesEqual(model.name, osc.name)) {throw new InvalidClassException(osc.name,
                    "local class name incompatible with stream class " +
                            "name "" + model.name + """);
        }

        if(! model.isEnum) {if((model.serializable == osc.serializable) && (model.externalizable ! = osc.externalizable)) {throw new InvalidClassException(osc.name,
                        "Serializable incompatible with Externalizable");
            }

            if((model.serializable ! = osc.serializable) || (model.externalizable ! = osc.externalizable) || ! (model.serializable || model.externalizable)) { deserializeEx =new ExceptionInfo(
                        osc.name, "class invalid for deserialization"); }}}// Initialize parameter assignment
    this.cl = cl;
    this.resolveEx = resolveEx;
    this.superDesc = superDesc;
    name = model.name;
    this.suid = suid;
    isProxy = false;
    isEnum = model.isEnum;
    serializable = model.serializable;
    externalizable = model.externalizable;
    hasBlockExternalData = model.hasBlockExternalData;
    hasWriteObjectData = model.hasWriteObjectData;
    fields = model.fields;
    primDataSize = model.primDataSize;
    numObjFields = model.numObjFields;

    if(osc ! =null) {
        localDesc = osc;
        writeObjectMethod = localDesc.writeObjectMethod;
        readObjectMethod = localDesc.readObjectMethod;
        readObjectNoDataMethod = localDesc.readObjectNoDataMethod;
        writeReplaceMethod = localDesc.writeReplaceMethod;
        readResolveMethod = localDesc.readResolveMethod;
        if (deserializeEx == null) {
            deserializeEx = localDesc.deserializeEx;
        }
        cons = localDesc.cons;
    }

    fieldRefl = getReflector(fields, localDesc);
    // reassign to matched fields so as to reflect local unshared settings
    fields = fieldRefl.getFields();
    initialized = true;
}
Copy the code

Let’s go back to the readOrdinaryObject() method and look at the readSerialData() method to get the property data.

private void readSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();// Get the class data array
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;

        if (slots[i].hasData) {
            if (obj == null|| handles.lookupException(passHandle) ! =null) {
                defaultReadFields(null, slotDesc); // skip field values
            } else if (slotDesc.hasReadObjectMethod()) {// If you override the readObject() method
                SerialCallbackContext oldContext = curContext;
                if(oldContext ! =null)
                    oldContext.check();
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);

                    bin.setBlockDataMode(true);
                    slotDesc.invokeReadObject(obj, this);
                } catch (ClassNotFoundException ex) {
                    /* * In most cases, the handle table has already * propagated a CNFException to passHandle at this * point; this mark call is included to address cases * where the custom readObject method has cons'ed and * thrown a new CNFException of its own. */
                    handles.markException(passHandle, ex);
                } finally {
                    curContext.setUsed();
                    if(oldContext! =null)
                        oldContext.check();
                    curContext = oldContext;
                    // END Android-changed: ThreadDeath cannot cause corruption on Android.
                }

                /* * defaultDataEnd may have been set indirectly by custom * readObject() method when calling defaultReadObject() or * readFields(); clear it to restore normal read behavior. */
                defaultDataEnd = false;
            } else {
                defaultReadFields(obj, slotDesc);// will be executed up to this point
                }

            if (slotDesc.hasWriteObjectData()) {
                skipCustomData();
            } else {
                bin.setBlockDataMode(false); }}else {
            if(obj ! =null &&
                slotDesc.hasReadObjectNoDataMethod() &&
                handles.lookupException(passHandle) == null) { slotDesc.invokeReadObjectNoData(obj); }}}}Copy the code

Enter the defaultReadFields() method.

private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOException { Class<? > cl = desc.forClass(); if (cl ! = null && obj ! = null && ! cl.isInstance(obj)) { throw new ClassCastException(); } int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize]; } bin.readFully(primVals, 0, primDataSize, false); // Read the basic data type from the stream if (obj! = null) { desc.setPrimFieldValues(obj, primVals); Int objHandle = passHandle; // Set the value of the basic data type to the serialized class. ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; For (int I = 0; i < objVals.length; i++) { ObjectStreamField f = fields[numPrimFields + i]; objVals[i] = readObject0(f.isUnshared()); // Recursively call back to readObject0() and process according to different data types. if (f.getField() ! = null) { handles.markDependency(objHandle, passHandle); } } if (obj ! = null) { desc.setObjFieldValues(obj, objVals); } passHandle = objHandle; }Copy the code

At this point, the deserialization is complete.

4. Flow chart of serialization and deserialization

5. Summarize the problem

  1. Implementation of Serializable interface is to play a role of identity, no substantive significance;
  2. SerialVersionUID is used to determine the version of serialized classes. The purpose of rewriting serialVersionUID is to prevent errors due to inconsistent serialVersionUID generated by default due to changes in serialized classes.
  3. Transient and static attributes are filtered directly from getDefaultSerialFields() when ObjectStreamClass is initialized;