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:
- Why should Serializable be implemented?
- Why should serialization override serialVersionUID and what does it do?
- 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
- The ObjectStreamClass object is initialized with objectStreamClass.lookup (CL, true)
- 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
- Read the description of the serialized class stored by calling the readClassDescriptor() method, and initialize the ObjectStreamClass object, assigning the readDesc attribute;
- 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
- Implementation of Serializable interface is to play a role of identity, no substantive significance;
- 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.
- Transient and static attributes are filtered directly from getDefaultSerialFields() when ObjectStreamClass is initialized;