iv.ObjectInputStream源码分析
上文解析了ObjectOutputStream类的代码,对于ObjectInputStream的解析本文不会像ObjectOutputStream类中的方法这样详细解析,因为ObjectInputStream类里面的大部分方法以及接口刚好是和ObjectOutputStream类中的方法对应,本文只提供一些比较特殊的代码解析,至于ObjectInputStream类的其他源代码希望读者自行阅读并理解。下一个章节会提供Java序列化的应用例子,到时候再来详细看看ObjectOutputStream和ObjectInputStream两个类的使用;
1)类定义:
ObjectInputStream的类定义如下:
- public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
该类的定义和ObjectOutputStream的定义大同小异,这里不重复ObjectStreamConstants接口了,看看InputStream类和ObjectInput接口的职责:
- InputStream是一个抽象类,它表示字节输入流的所有类的超类,需要定义InputStream子类的应用程序必须总是提供返回下一个输入字节的方法;
- ObjectInput接口扩展DataInput接口包含对象的操作,DataInput接口包括了基本类型的输入方法,ObjectInput扩展了该接口,以包含对象、数组和String的输入方法;
ObjectInputStream就是一个实现了ObjectInput接口的InputStream的子类;
2)内部类区别
ObjectInputStream类中的内部类定义如下:
- private static class Caches
- public static abstract class GetField
- private class GetFieldImpl extends GetField
- private static class ValidationList
- private static class PeekInputStream extends InputStream
- private class BlockDataInputStream extends InputStream implements DataInput
- private static class HandleTable
Caches
该内部类的定义和ObjectOutputStream类中的缓存类定义是一致的,它拥有两个成员subclassAudits和subclassAuditsQueue,其类型这里就不讲解了;
GetField/GetFieldImpl
该内部类和PutField/PutFieldImpl配对使用,主要用于处理对象中字段信息的读取操作:
BlockDataInputStream
这个类和BlockDataOutputStream类对应,主要用于读取基础类型的数据信息,它属于ObjectInputStream中的“隐藏输入流【underlying stream】”;
成员属性
- /** end offset of valid data in buf, or -1 if no more block data */
- private int end = -1;
- /** number of bytes in current block yet to be read from stream */
- private int unread = 0;
BlockDataInputStream中多了两个偏移量,除了基本偏移量以外还包含另外两个成员属性偏移量:
- end
表示读取某一段Data Block时该数据块的结束偏移量,如果该数据块中没有合法数据,则返回-1; - unread
用来统计当前Data Block中未读取的字节的数量;
成员函数
currentBlockRemaining
- int currentBlockRemaining() {
- if (blkmode) {
- return (end >= 0) ? (end - pos) + unread : 0;
- } else {
- throw new IllegalStateException();
- }
- }
因为是读取,该方法主要用于计算在Data Block模式下一个数据块中还未使用的字节的数量,如果当前模式不是Data Block模式,则抛出IllegalStateException异常信息;
skip*方法
- void skipBlockData() throws IOException {
- if (!blkmode) {
- throw new IllegalStateException("not in block data mode");
- }
- while (end >= 0) {
- refill();
- }
- }
- public long skip(long len) throws IOException {
- long remain = len;
- while (remain > 0) {
- if (blkmode) {
- if (pos == end) {
- refill();
- }
- if (end < 0) {
- break;
- }
- int nread = (int) Math.min(remain, end - pos);
- remain -= nread;
- pos += nread;
- } else {
- int nread = (int) Math.min(remain, MAX_BLOCK_SIZE);
- if ((nread = in.read(buf, 0, nread)) < 0) {
- break;
- }
- remain -= nread;
- }
- }
- return len - remain;
- }
因为是输入流,所以在读取信息过程可以直接跳过,所以这两个成员函数主要负责在Data Block读取中跳过一些不需要读取的数据信息;
HandleTable
这个类和ObjectOutputStream类中的HandleTable同样对应。但是这个类的作用不一样:
该类为一个不同步的表结构,它用于跟踪指向对象的句柄这种映射关系,以及和反序列化对象相关联的ClassNotFoundException异常。这个类的实现中使用了异常传播算法,它用于判断哪一个类在反序列化过程抛出了ClassNotFoundException异常,将这些异常信息放入对象图中连续或者不连续的生命周期内。这个类通常的使用方式如下:
在反序列化过程中,一个对象第一次调用assign方法赋值给一个引用Handle,这个方法将会把赋值过的引用Handle设置成“open”状态,其中如果依赖其他异常信息的引用Handle能够通过调用markDependency方法来标记注册,或者调用markException直接将该Handle和异常联系起来。如果一个Handle标记了一个异常,当前HandleTable将重新负责异常的传播,它会将异常信息传播给所有依赖当前对象的对象;
一旦Handle引用的所有的异常信息或者依赖都被注册过后,这个引用Handle就会调用finish方法设置成“close”状态。完成一个引用Handle的行为将允许异常传播算法积极地去剪断依赖链接,其目的是为了减少性能和内存的开销;
*:异常传播算法依赖于Handle,因为它的assigned/finished操作都使用的LIFO顺序,这样更加简单;尽管如此,它不能够强制执行这些约束;
静态属性
- private static final byte STATUS_OK = 1;
- private static final byte STATUS_UNKNOWN = 2;
- private static final byte STATUS_EXCEPTION = 3;
读取过程中数据的状态信息
- STATUS_OK
表示当前读取的数据是合法数据; - STATUS_UNKNOWN
表示当前读取的对象数据为“Unknown”对象,即无法识别; - STATUS_EXCEPTION
表示当前读取数据的过程出现了异常信息
成员函数
- void markDependency(int dependent, int target) {
- if (dependent == NULL_HANDLE || target == NULL_HANDLE) {
- return;
- }
- switch (status[dependent]) {
- case STATUS_UNKNOWN:
- switch (status[target]) {
- case STATUS_OK:
- // ignore dependencies on objs with no exception
- break;
- case STATUS_EXCEPTION:
- // eagerly propagate exception
- markException(dependent,
- (ClassNotFoundException) entries[target]);
- break;
- case STATUS_UNKNOWN:
- // add to dependency list of target
- if (deps[target] == null) {
- deps[target] = new HandleList();
- }
- deps[target].add(dependent);
- // remember lowest unresolved target seen
- if (lowDep < 0 || lowDep > target) {
- lowDep = target;
- }
- break;
- default:
- throw new InternalError();
- }
- break;
- case STATUS_EXCEPTION:
- break;
- default:
- throw new InternalError();
- }
- }
- void markException(int handle, ClassNotFoundException ex) {
- switch (status[handle]) {
- case STATUS_UNKNOWN:
- status[handle] = STATUS_EXCEPTION;
- entries[handle] = ex;
- // propagate exception to dependents
- HandleList dlist = deps[handle];
- if (dlist != null) {
- int ndeps = dlist.size();
- for (int i = 0; i < ndeps; i++) {
- markException(dlist.get(i), ex);
- }
- deps[handle] = null;
- }
- break;
- case STATUS_EXCEPTION:
- break;
- default:
- throw new InternalError();
- }
- }
这两个方法为上边提到的markDependency和markException方法,本文不探讨它的细节;
PeekInputStream
这个类是ObjectOutputStream中没有对应的类,它使得输入流可支持栈顶单字节读取操作,其定义如下:
- private static class PeekInputStream extends InputStream {
- /** underlying stream */
- private final InputStream in;
- /** peeked byte */
- private int peekb = -1;
- /**
- * Creates new PeekInputStream on top of given underlying stream.
- */
- PeekInputStream(InputStream in) {
- this.in = in;
- }
- /**
- * Peeks at next byte value in stream. Similar to read(), except
- * that it does not consume the read value.
- */
- int peek() throws IOException {
- return (peekb >= 0) ? peekb : (peekb = in.read());
- }
- public int read() throws IOException {
- if (peekb >= 0) {
- int v = peekb;
- peekb = -1;
- return v;
- } else {
- return in.read();
- }
- }
- public int read(byte[] b, int off, int len) throws IOException {
- if (len == 0) {
- return 0;
- } else if (peekb < 0) {
- return in.read(b, off, len);
- } else {
- b[off++] = (byte) peekb;
- len--;
- peekb = -1;
- int n = in.read(b, off, len);
- return (n >= 0) ? (n + 1) : 1;
- }
- }
- void readFully(byte[] b, int off, int len) throws IOException {
- int n = 0;
- while (n < len) {
- int count = read(b, off + n, len - n);
- if (count < 0) {
- throw new EOFException();
- }
- n += count;
- }
- }
- public long skip(long n) throws IOException {
- if (n <= 0) {
- return 0;
- }
- int skipped = 0;
- if (peekb >= 0) {
- peekb = -1;
- skipped++;
- n--;
- }
- return skipped + skip(n);
- }
- public int available() throws IOException {
- return in.available() + ((peekb >= 0) ? 1 : 0);
- }
- public void close() throws IOException {
- in.close();
- }
- }
ValidationList
该类在ObjectOutputStream中也没有对应的定义,它主要负责处理反序列化完成过后的回调信息;
- private static class ValidationList {
- private static class Callback {
- final ObjectInputValidation obj;
- final int priority;
- Callback next;
- final AccessControlContext acc;
- Callback(ObjectInputValidation obj, int priority, Callback next,
- AccessControlContext acc)
- {
- this.obj = obj;
- this.priority = priority;
- this.next = next;
- this.acc = acc;
- }
- }
- private Callback list;
- ValidationList() {
- }
- void register(ObjectInputValidation obj, int priority)
- throws InvalidObjectException
- {
- if (obj == null) {
- throw new InvalidObjectException("null callback");
- }
- Callback prev = null, cur = list;
- while (cur != null && priority < cur.priority) {
- prev = cur;
- cur = cur.next;
- }
- AccessControlContext acc = AccessController.getContext();
- if (prev != null) {
- prev.next = new Callback(obj, priority, cur, acc);
- } else {
- list = new Callback(obj, priority, list, acc);
- }
- }
- void doCallbacks() throws InvalidObjectException {
- try {
- while (list != null) {
- AccessController.doPrivileged(
- new PrivilegedExceptionAction<Void>()
- {
- public Void run() throws InvalidObjectException {
- list.obj.validateObject();
- return null;
- }
- }, list.acc);
- list = list.next;
- }
- } catch (PrivilegedActionException ex) {
- list = null;
- throw (InvalidObjectException) ex.getException();
- }
- }
- public void clear() {
- list = null;
- }
- }
从ObjectInputStream的类定义可以知道,反序列化过程中没有调试辅助信息代码段,它没有定义类似java.io.ObjectOutputStream.DebugTraceInfoStack的类用来作为调试的辅助工具类;而反序列化的内部类定义了新的结构ValidationList用于做反序列化过后的回调执行;它同样没有ReplaceTable来保存“替换”对象的信息——实际上反序列化中不需要保存“替换”对象的信息,因为执行反序列化的时候它会根据对象的性质直接将这个对象“Resolve”并且还原成Java对象或者Java引用,对它而言也没有必要使用ReplaceTable来存储“替换”对象的信息;
3)静态成员:
和ObjectOutputStream不太一样的是ObjectInputStream中多了静态成员:
- /** handle value representing null */
- private static final int NULL_HANDLE = -1;
- /** marker for unshared objects in internal handle table */
- private static final Object unsharedMarker = new Object();
- /** table mapping primitive type names to corresponding class objects */
- private static final HashMap<String, Class<?>> primClasses
- = new HashMap<>(8, 1.0F);
这些静态成员的解析如下:
- NULL_HANDLE
该成员表示null值,因为读取过程引用Handle是一个整数,所以用整数中的-1表示null引用; - unsharedMarker
该对象主要用于在内部的“引用哈希表”中标记“unshared”状态的对象——“unshared”状态的对象的重建步骤和非“unshared”不一样; - primClasses
该哈希表用于存储基础类型名称到对应的类实例的映射,它在静态初始化块中会被初始化:
- static {
- primClasses.put("boolean", boolean.class);
- primClasses.put("byte", byte.class);
- primClasses.put("char", char.class);
- primClasses.put("short", short.class);
- primClasses.put("int", int.class);
- primClasses.put("long", long.class);
- primClasses.put("float", float.class);
- primClasses.put("double", double.class);
- primClasses.put("void", void.class);
- }
4)成员属性:
ObjectInputStream中的成员属性和ObjectOutputStream中的大部分都是对应的,先看看它的定义内容:
- /** filter stream for handling block data conversion */
- private final BlockDataInputStream bin;
- /** validation callback list */
- private final ValidationList vlist;
- /** recursion depth */
- private int depth;
- /** whether stream is closed */
- private boolean closed;
- /** wire handle -> obj/exception map */
- private final HandleTable handles;
- /** scratch field for passing handle values up/down call stack */
- private int passHandle = NULL_HANDLE;
- /** flag set when at end of field value block with no TC_ENDBLOCKDATA */
- private boolean defaultDataEnd = false;
- /** buffer for reading primitive field values */
- private byte[] primVals;
- /** if true, invoke readObjectOverride() instead of readObject() */
- private final boolean enableOverride;
- /** if true, invoke resolveObject() */
- private boolean enableResolve;
- /**
- * Context during upcalls to class-defined readObject methods; holds
- * object currently being deserialized and descriptor for current class.
- * Null when not during readObject upcall.
- */
- private SerialCallbackContext curContext;
这里保留了注释,让读者可以理解每一个属性的作用,讲几个在ObjectInputStream中具有特殊作用的成员属性,其他的读者可以和ObjectOutputStream中的成员属性对比分析:
- vlist——java.io.ObjectInputStream.ValidationList
该成员属性的概念类型为一个“验证链”,它主要用于提供一个回调操作的验证集合; - closed——boolean
判断当前字节流是否已经处于“closed”状态,如果已经是“closed”了,则不需要再从字节流中读取数据; - defaultDataEnd——boolean
该标记表示一个数据段的结束,针对Data Block模式的数据段而言它的结束会出现一个TC_ENDBLOCKDATA标记,该成员属性是用于判断非Data Block模式的数据段的结束——这种情况下无法读取TC_ENDBLOCKDATA来判断; - primVals——byte[]
该成员属性表示从字节流中读取的所有对象中基础类型的字段值的集合,前文分析二进制序列时已经知道,对象中的成员属性值是直接输出的,它没有任何标记来识别,而primVals成员属性中就存储了所有基础类型的成员属性的值的集合;
5)本地方法:
同ObjectOutputStream一样,在ObjectInputStream中也定义了本地方法,只是它的本地方法比ObjectOutputStream多了一个:
- /**
- * Converts specified span of bytes into float values.
- */
- // REMIND: remove once hotspot inlines Float.intBitsToFloat
- private static native void bytesToFloats(byte[] src, int srcpos,
- float[] dst, int dstpos,
- int nfloats);
- /**
- * Converts specified span of bytes into double values.
- */
- // REMIND: remove once hotspot inlines Double.longBitsToDouble
- private static native void bytesToDoubles(byte[] src, int srcpos,
- double[] dst, int dstpos,
- int ndoubles);
- /**
- * Returns the first non-null class loader (not counting class loaders of
- * generated reflection implementation classes) up the execution stack, or
- * null if only code from the null class loader is on the stack. This
- * method is also called via reflection by the following RMI-IIOP class:
- *
- * com.sun.corba.se.internal.util.JDKClassLoader
- *
- * This method should not be removed or its signature changed without
- * corresponding modifications to the above class.
- */
- // REMIND: change name to something more accurate?
- private static native ClassLoader latestUserDefinedLoader();
除了针对浮点数读取的本地方法以外,它还包含了一个latestUserDefinedLoader方法,该方法主要用于返回系统中第一个非null值的类加载器,这个类加载器位于执行堆栈的栈顶。除了使用native本地方法可以获得该类加载器以外,还可以通过RMI-IIOP中的反射方法获得:com.sun.corba.se.internal.util.JDKClassLoader。JVM在反序列化Java对象时,它会在ClassLoader中重建Java对象,对于同一个包中的Java对象,如果使用的类加载器不同,则也会抛出ClassNotFoundException异常信息——除了ClassLoader无法找到系统中的类会抛出该异常,如果使用不同的ClassLoader加载的类相互调用时也会抛出该异常。所以该方法设计的目的应该是使得所有重建Java对象的类加载器保持使用统一的类加载器,这样不会因为使用了不同的ClassLoader而抛出该异常。
6)DataInput接口
DataInput接口是ObjectInput接口的父接口,而ObjectInputStream实现了ObjectInput接口,先看看DataInput接口中的方法定义:
这些接口方法在ObjectInputStream中的代码实现如下:
- public boolean readBoolean() throws IOException {
- return bin.readBoolean();
- }
- public byte readByte() throws IOException {
- return bin.readByte();
- }
- public int readUnsignedByte() throws IOException {
- return bin.readUnsignedByte();
- }
- public char readChar() throws IOException {
- return bin.readChar();
- }
- public short readShort() throws IOException {
- return bin.readShort();
- }
- public int readUnsignedShort() throws IOException {
- return bin.readUnsignedShort();
- }
- public int readInt() throws IOException {
- return bin.readInt();
- }
- public long readLong() throws IOException {
- return bin.readLong();
- }
- public float readFloat() throws IOException {
- return bin.readFloat();
- }
- public double readDouble() throws IOException {
- return bin.readDouble();
- }
- public void readFully(byte[] buf) throws IOException {
- bin.readFully(buf, 0, buf.length, false);
- }
- public void readFully(byte[] buf, int off, int len) throws IOException {
- int endoff = off + len;
- if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) {
- throw new IndexOutOfBoundsException();
- }
- bin.readFully(buf, off, len, false);
- }
- public int skipBytes(int len) throws IOException {
- return bin.skipBytes(len);
- }
- @Deprecated
- public String readLine() throws IOException {
- return bin.readLine();
- }
- public String readUTF() throws IOException {
- return bin.readUTF();
- }
实际上和ObjectOutputStream中定义的“隐藏字节流【underlying stream】”一样,ObjectInputStream在实现这些read*方法时调用了bin【java.io.ObjectInputStream.BlockDataInputStream】成员函数中的方法;
7)ObjectInput接口
接下来再看看ObjectInput接口中的方法实现,其整体定义如下:
ObjectInputStream类中实现了这些方法,先保留readObject方法,看看其他几个方法:
- public int available() throws IOException {
- return bin.available();
- }
- public void close() throws IOException {
- /*
- * Even if stream already closed, propagate redundant close to
- * underlying stream to stay consistent with previous implementations.
- */
- closed = true;
- if (depth == 0) {
- clear();
- }
- bin.close();
- }
需要注意一点:skip方法因为已经在父类InputStream中提供了默认实现,所以ObjectInputStream中并没有提供skip方法的实现代码;
【*:ObjectOutputStream中提供了关于流协议的方法,但是在ObjectInputStream方法中,并没有提供任何流协议相关的方法。】
8)readFields
该方法和putFields方法对应,而且ObjectInputStream类中没有和writeFields对应的方法,它的代码定义如下:
- public ObjectInputStream.GetField readFields()
- throws IOException, ClassNotFoundException
- {
- if (curContext == null) {
- throw new NotActiveException("not in call to readObject");
- }
- Object curObj = curContext.getObj();
- ObjectStreamClass curDesc = curContext.getDesc();
- bin.setBlockDataMode(false);
- GetFieldImpl getField = new GetFieldImpl(curDesc);
- getField.readFields();
- bin.setBlockDataMode(true);
- if (!curDesc.hasWriteObjectData()) {
- /*
- * Fix for 4360508: since stream does not contain terminating
- * TC_ENDBLOCKDATA tag, set flag so that reading code elsewhere
- * knows to simulate end-of-custom-data behavior.
- */
- defaultDataEnd = true;
- }
- return getField;
- }
这个方法用于读取类定义中的字段信息,注意——该方法的调用必须在readObject方法内调用,不能在其他位置调用;从代码注释中可以知道:其中有一段代码是为了处理4360508这个Bug而存在的;
9)read*单元方法
接下来看看ObjectInputStream类中read*私有单元方法,这些方法主要用于读取TC_*标记段,这些方法在读取字节时都会先判断读取的第一个字节是否对应区段的TC_*标记的值,如果这个值不匹配则抛出java.lang.InternalError信息;
readFataException
- private IOException readFatalException() throws IOException {
- if (bin.readByte() != TC_EXCEPTION) {
- throw new InternalError();
- }
- clear();
- return (IOException) readObject0(false);
- }
该方法主要用于读取TC_EXCEPTION标记段,这个方法的实现和writeFataException方法是完全对应的;
readNull
- private Object readNull() throws IOException {
- if (bin.readByte() != TC_NULL) {
- throw new InternalError();
- }
- passHandle = NULL_HANDLE;
- return null;
- }
从字节流中读取一个TC_NULL标记段,如果是引用Handle则读取一个NULL_HANDLE;
readHandle
- private Object readHandle(boolean unshared) throws IOException {
- if (bin.readByte() != TC_REFERENCE) {
- throw new InternalError();
- }
- passHandle = bin.readInt() - baseWireHandle;
- if (passHandle < 0 || passHandle >= handles.size()) {
- throw new StreamCorruptedException(
- String.format("invalid handle value: %08X", passHandle +
- baseWireHandle));
- }
- if (unshared) {
- // REMIND: what type of exception to throw here?
- throw new InvalidObjectException(
- "cannot read back reference as unshared");
- }
- Object obj = handles.lookupObject(passHandle);
- if (obj == unsharedMarker) {
- // REMIND: what type of exception to throw here?
- throw new InvalidObjectException(
- "cannot read back reference to unshared object");
- }
- return obj;
- }
该方法从字节流中读取TC_REFERENCE标记段,它会把读取的引用Handle赋值给passHandle变量。注意该方法会抛出三个异常信息【除开InternalError】:
- java.io.StreamCorruptedException
如果passHandle的值大于handles中的尺寸,或者通过和baseWireHandle计算过后的值小于0,则抛出该异常信息; - java.io.InvalidObjectException
如果调用readHandle方法时使用了“unshared”的方法则抛出该异常信息; - java.io.InvalidObjectException
如果从handles中获取的对象和unsharedMarker引用的对象是同一个对象则抛出该异常信息;
readClass
- private Class readClass(boolean unshared) throws IOException {
- if (bin.readByte() != TC_CLASS) {
- throw new InternalError();
- }
- ObjectStreamClass desc = readClassDesc(false);
- Class cl = desc.forClass();
- passHandle = handles.assign(unshared ? unsharedMarker : cl);
- ClassNotFoundException resolveEx = desc.getResolveException();
- if (resolveEx != null) {
- handles.markException(passHandle, resolveEx);
- }
- handles.finish(passHandle);
- return cl;
- }
该方法从字节流中读取TC_CLASS标记段的信息;
readProxyDesc
- private ObjectStreamClass readProxyDesc(boolean unshared)
- throws IOException
- {
- if (bin.readByte() != TC_PROXYCLASSDESC) {
- throw new InternalError();
- }
- ObjectStreamClass desc = new ObjectStreamClass();
- int descHandle = handles.assign(unshared ? unsharedMarker : desc);
- passHandle = NULL_HANDLE;
- int numIfaces = bin.readInt();
- String[] ifaces = new String[numIfaces];
- for (int i = 0; i < numIfaces; i++) {
- ifaces[i] = bin.readUTF();
- }
- Class cl = null;
- ClassNotFoundException resolveEx = null;
- bin.setBlockDataMode(true);
- try {
- if ((cl = resolveProxyClass(ifaces)) == null) {
- resolveEx = new ClassNotFoundException("null class");
- }
- } catch (ClassNotFoundException ex) {
- resolveEx = ex;
- }
- skipCustomData();
- desc.initProxy(cl, resolveEx, readClassDesc(false));
- handles.finish(descHandle);
- passHandle = descHandle;
- return desc;
- }
该方法从字节流中读取TC_PROXYCLASSDESC标记段的信息,该方法的流程如下:
- 先判断TC_PROXYCLASSDESC标记看是否需要抛出InternalError;
- 如果使用的读取模式是非共享模式,则将从引用->对象的映射中读取一个新的desc,否则直接读取unsharedMarker中读取对应的对象;
- 构建接口名称数组ifaces;
- 开启Data Block模式然后调用resolveProxyClass方法处理每一个接口内容;
- 调用skipCustomData方法跳过自定义信息的读取;
- 调用ObjectStreamClass中的initProxy方法来初始化cl;
- 调用handles的finish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性;
readNonProxyDesc
- 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();
- } catch (ClassNotFoundException ex) {
- throw (IOException) new InvalidClassException(
- "failed to read class descriptor").initCause(ex);
- }
- Class cl = null;
- ClassNotFoundException resolveEx = null;
- bin.setBlockDataMode(true);
- try {
- if ((cl = resolveClass(readDesc)) == null) {
- resolveEx = new ClassNotFoundException("null class");
- }
- } catch (ClassNotFoundException ex) {
- resolveEx = ex;
- }
- skipCustomData();
- desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
- handles.finish(descHandle);
- passHandle = descHandle;
- return desc;
- }
该方法从字节流中读取TC_CLASSDESC标记段的信息,该方法的流程如下:
- 先判断TC_CLASSDESC标记看是否需要抛出InternalError;
- 如果使用的读取模式是非共享模式,则将从引用->对象的映射中读取一个新的desc,否则直接读取unsharedMarker中读取对应的对象;
- 调用readClassDescriptor方法读取当前类的元数据信息;
- 开启Data Block模式然后调用resolveClass方法处理当前类的信息;
- 调用skipCustomData方法跳过自定义信息的读取;
- 调用ObjectStreamClass中的initNonProxy方法初始化cl;
- 调用handles的finish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性;
readString
- private String readString(boolean unshared) throws IOException {
- String str;
- byte tc = bin.readByte();
- switch (tc) {
- case TC_STRING:
- str = bin.readUTF();
- break;
- case TC_LONGSTRING:
- str = bin.readLongUTF();
- break;
- default:
- throw new StreamCorruptedException(
- String.format("invalid type code: %02X", tc));
- }
- passHandle = handles.assign(unshared ? unsharedMarker : str);
- handles.finish(passHandle);
- return str;
- }
该方法从字节流中读取TC_STRING和TC_LONGSTRING标记段信息,该方法的流程如下:
- 先从系统中读取字节标记:
如果标记值为TC_STRING,则调用readUTF方法读取String信息;
如果标记值为TC_LONGSTRING,则调用readLongUTF方法读取String信息;
如果两个标记都不是则抛出java.io.StreamCorruptedException异常信息; - 调用handles的assign方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性,然后调用finish方法;
readArray
- private Object readArray(boolean unshared) throws IOException {
- if (bin.readByte() != TC_ARRAY) {
- throw new InternalError();
- }
- ObjectStreamClass desc = readClassDesc(false);
- int len = bin.readInt();
- Object array = null;
- Class cl, ccl = null;
- if ((cl = desc.forClass()) != null) {
- ccl = cl.getComponentType();
- array = Array.newInstance(ccl, len);
- }
- int arrayHandle = handles.assign(unshared ? unsharedMarker : array);
- ClassNotFoundException resolveEx = desc.getResolveException();
- if (resolveEx != null) {
- handles.markException(arrayHandle, resolveEx);
- }
- if (ccl == null) {
- for (int i = 0; i < len; i++) {
- readObject0(false);
- }
- } else if (ccl.isPrimitive()) {
- if (ccl == Integer.TYPE) {
- bin.readInts((int[]) array, 0, len);
- } else if (ccl == Byte.TYPE) {
- bin.readFully((byte[]) array, 0, len, true);
- } else if (ccl == Long.TYPE) {
- bin.readLongs((long[]) array, 0, len);
- } else if (ccl == Float.TYPE) {
- bin.readFloats((float[]) array, 0, len);
- } else if (ccl == Double.TYPE) {
- bin.readDoubles((double[]) array, 0, len);
- } else if (ccl == Short.TYPE) {
- bin.readShorts((short[]) array, 0, len);
- } else if (ccl == Character.TYPE) {
- bin.readChars((char[]) array, 0, len);
- } else if (ccl == Boolean.TYPE) {
- bin.readBooleans((boolean[]) array, 0, len);
- } else {
- throw new InternalError();
- }
- } else {
- Object[] oa = (Object[]) array;
- for (int i = 0; i < len; i++) {
- oa[i] = readObject0(false);
- handles.markDependency(arrayHandle, passHandle);
- }
- }
- handles.finish(arrayHandle);
- passHandle = arrayHandle;
- return array;
- }
该方法从字节流中读取TC_ARRAY标记段,其详细流程如下:
- 先从系统中读取TC_ARRAY标记,判断是否抛出InternalError;
- 读取Array数组的类描述信息【元数据信息】;
- 然后读取Array数组的长度;
- 从系统中读取Array数组中元素的类型信息;
- 从handles成员属性中得到当前数组的整数引用Handle信息;
- 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
- 如果Array数组中的元素是基础类型则执行基础类型的反序列化操作,若获取的类型为null则调用readObject0方法反序列化该对象,否则先调用readObject0方法反序列化该对象,然后调用markDependency标记依赖;
- 调用handles的finish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性;
readEnum
- private Enum readEnum(boolean unshared) throws IOException {
- if (bin.readByte() != TC_ENUM) {
- throw new InternalError();
- }
- ObjectStreamClass desc = readClassDesc(false);
- if (!desc.isEnum()) {
- throw new InvalidClassException("non-enum class: " + desc);
- }
- int enumHandle = handles.assign(unshared ? unsharedMarker : null);
- ClassNotFoundException resolveEx = desc.getResolveException();
- if (resolveEx != null) {
- handles.markException(enumHandle, resolveEx);
- }
- String name = readString(false);
- Enum en = null;
- Class cl = desc.forClass();
- if (cl != null) {
- try {
- en = Enum.valueOf(cl, name);
- } catch (IllegalArgumentException ex) {
- throw (IOException) new InvalidObjectException(
- "enum constant " + name + " does not exist in " +
- cl).initCause(ex);
- }
- if (!unshared) {
- handles.setObject(enumHandle, en);
- }
- }
- handles.finish(enumHandle);
- passHandle = enumHandle;
- return en;
- }
该方法从字节流中读取TC_ENUM标记段,其详细流程如下:
- 从系统中读取TC_ENUM标记,判断是否抛出InternalError;
- 从系统中读取Enum类型的类描述信息,如果发现类型不为Enum则抛出java.io.InvalidClassException异常;
- 从handles成员属性中得到当前数组的整数引用Handle信息;
- 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
- 从系统中读取枚举常量的字符串字面量【调用name()方法得到的字符串】;
- 实例化读取到的Enum类型;
- 调用handles的finish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性;
readOridinaryObject
- private Object readOrdinaryObject(boolean unshared)
- throws IOException
- {
- if (bin.readByte() != TC_OBJECT) {
- throw new InternalError();
- }
- ObjectStreamClass desc = readClassDesc(false);
- desc.checkDeserialize();
- 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);
- }
- 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) {
- handles.setObject(passHandle, obj = rep);
- }
- }
- return obj;
- }
该方法从字节流中读取TC_OBJECT标记段,其详细流程如下:
- 从系统中读取TC_OBJECT标记,判断是否抛出InternalError;
- 检查当前处理的对象是否是一个可反序列化对象【即检查它是否具有“可序列化的语义”】;
- 从系统中读取当前Java对象所属类的描述信息【类元数据信息】;
- 调用handles的finish方法完成引用Handle的赋值操作,将结果赋值给passHandle成员属性;
- 调用ObjectStreamClass中的getResolveException获取“Resolve”过程中的异常信息,如果出现异常则调用markException方法标记;
- 如果当前对象实现了Externalizable接口则调用readExternalData将对象数据写入该对象,若它实现的是Serializable接口则调用readSerialData方法执行反序列化;
- 调用handles的finish方法完成引用Handle的赋值操作;
- 判断读取对象是否实现了readResolve方法,如果实现了该方法并且引用Handle并没指向一个异常信息且获得的对象不为null时,则调用该对象的readResolve方法;
10)其他read*单元方法
readExternalData
- private void readExternalData(Externalizable obj, ObjectStreamClass desc)
- throws IOException
- {
- SerialCallbackContext oldContext = curContext;
- curContext = null;
- try {
- boolean blocked = desc.hasBlockExternalData();
- if (blocked) {
- bin.setBlockDataMode(true);
- }
- if (obj != null) {
- try {
- obj.readExternal(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 readExternal
- * method has cons'ed and thrown a new CNFException of its
- * own.
- */
- handles.markException(passHandle, ex);
- }
- }
- if (blocked) {
- skipCustomData();
- }
- } finally {
- curContext = oldContext;
- }
- /*
- * At this point, if the externalizable data was not written in
- * block-data form and either the externalizable class doesn't exist
- * locally (i.e., obj == null) or readExternal() just threw a
- * CNFException, then the stream is probably in an inconsistent state,
- * since some (or all) of the externalizable data may not have been
- * consumed. Since there's no "correct" action to take in this case,
- * we mimic the behavior of past serialization implementations and
- * blindly hope that the stream is in sync; if it isn't and additional
- * externalizable data remains in the stream, a subsequent read will
- * most likely throw a StreamCorruptedException.
- */
- }
该方法的代码中保留了注释,实际上这个方法在系统得知反序列化的对象实现的是外部化而不是序列化时调用,它的详细流程如下:
- 和ObjectOutputStream中的writeExternal方法一样,它的调用需要将上下文环境变量curContext设置成null,调用结束后再还原;
- 先查看读取的字节流中是否包含了外部化的Data Block模式写入的数据,这个检测通过调用ObjectStreamClass的hasBlockExternalData方法完成,如果包含则先启用Data Block模式;
- 若传入的对象不为null,则调用对象本身的readExternal方法【外部化的类必须实现该方法】;
- readExternal方法调用完成过后,如果字节流中使用的模式是Data Block模式则直接跳过自定义数据段的读取;
- 异常部分的注释不解释了,这里看看最后这部分的注释:如果外部化的数据并没有以Data Block的形式写入字节流或者外部化的类并不存在于本地JVM环境,又或者readExternal方法引起了CNFException异常,当前字节流会因为外部化的数据并没有处理而处于“inconsistent【不一致】”的状态。如果环境中不存在“correct【修复】”行为,系统将模仿序列化实现的行为并且盲目地系统该字节流会同步数据而一致,如果字节流中并没有外部化数据,则随后的调用将会抛出java.io.StreamCorruptedException异常信息;
readSerialData
- private void readSerialData(Object obj, ObjectStreamClass desc)
- throws IOException
- {
- ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
- for (int i = 0; i < slots.length; i++) {
- ObjectStreamClass slotDesc = slots[i].desc;
- if (slots[i].hasData) {
- if (obj != null &&
- slotDesc.hasReadObjectMethod() &&
- handles.lookupException(passHandle) == null)
- {
- SerialCallbackContext oldContext = curContext;
- 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();
- curContext = oldContext;
- }
- /*
- * 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);
- }
- if (slotDesc.hasWriteObjectData()) {
- skipCustomData();
- } else {
- bin.setBlockDataMode(false);
- }
- } else {
- if (obj != null &&
- slotDesc.hasReadObjectNoDataMethod() &&
- handles.lookupException(passHandle) == null)
- {
- slotDesc.invokeReadObjectNoData(obj);
- }
- }
- }
- }
该方法在系统得知其反序列化对象具有“可序列化”的语义时将反序列化对象的相关信息,它的详细流程如下:
- 反序列化该对象之前,先从类描述信息【元数据信息】中获取ClassDataSlot的信息;
- 若ClassDataSlot的成员属性hasData标记有数据,则判断该反序列化对象是否重写了readObject方法
a.如果重写了该方法,则开启Data Block模式,再调用该对象的readObject方法,调用过后关闭Data Block模式;
b.如果没有重写该方法,则直接调用defaultReadFields方法读取该类中的每一个字段的值; - 如果ObjectOutputStream类使用writeObject方法写入了额外的自定义信息,则调用skipCustomData方法跳过读取,否则直接关闭Data Block模式;
- 如果ClassDataSlot的成员属性hasData标记没有序列化数据,则调用invokeReadObjectNoData方法来处理当前对象;
11)两个default
ObjectInputStream和前文中的ObjectOutputStream一样定义了两个default的默认方法,用于默认反序列化。
defaultReadObject
- public void defaultReadObject()
- throws IOException, ClassNotFoundException
- {
- if (curContext == null) {
- throw new NotActiveException("not in call to readObject");
- }
- Object curObj = curContext.getObj();
- ObjectStreamClass curDesc = curContext.getDesc();
- bin.setBlockDataMode(false);
- defaultReadFields(curObj, curDesc);
- bin.setBlockDataMode(true);
- if (!curDesc.hasWriteObjectData()) {
- /*
- * Fix for 4360508: since stream does not contain terminating
- * TC_ENDBLOCKDATA tag, set flag so that reading code elsewhere
- * knows to simulate end-of-custom-data behavior.
- */
- defaultDataEnd = true;
- }
- ClassNotFoundException ex = handles.lookupException(passHandle);
- if (ex != null) {
- throw ex;
- }
- }
该方法负责读取需要反序列化的对象,并且构建该对象信息;看看这个方法的详细流程:
- 这个方法只能在readObject方法内部调用,所以会检查curContext环境,如果上下文调用环境不对则抛出NotActiveException异常信息;
- 从上下文环境获取对象以及对象所属类的类描述信息;
- 关闭Data Block模式,并且调用defaultReadFields方法读取该类的字段信息,调用完成过后再开启Data Block模式;
- 检查对象是否重写了writeObject方法,如果没有重写该方法需要手动设置defaultDataEnd为true,从注释可以知道这段代码的目的是为了修复4360508的Bug而存在;
- 最后使用handles查找是否出现了ClassNotFoundException异常,如果出现该异常则表示对象重建过程失败,抛出该异常信息;
defaultReadFields
- private void defaultReadFields(Object obj, ObjectStreamClass desc)
- throws IOException
- {
- // REMIND: is isInstance check necessary?
- 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);
- if (obj != null) {
- desc.setPrimFieldValues(obj, primVals);
- }
- int objHandle = passHandle;
- 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());
- if (f.getField() != null) {
- handles.markDependency(objHandle, passHandle);
- }
- }
- if (obj != null) {
- desc.setObjFieldValues(obj, objVals);
- }
- passHandle = objHandle;
- }
该方法根据传入的对象以及对象的类描述信息读取字段数据【成员属性】相关信息;看看这个方法的详细流程:
- 先从传入的desc中获得类描述信息,如果传入对象和类不匹配则抛出java.lang.ClassCastException异常信息;
- 然后从desc中读取基本类型字段信息,并且读取基本字段的信息;
- 最后读取对象类型的字段信息,调用readObject0方法读取这些字段数据;
- 在读取过程中如果从ObjectStreamField得到的字段信息不为空还需要调用markDependency方法标记依赖关系;
- 若对象信息不为空则设置其获取到的所有字段【基础类型和对象类型】的值;
12)read*控制方法
除了上边提到的default和read*单元方法以外,ObjectInputStream类中还包含了许多read*方法,这些read*方法主要用于不同的情况对Java对象进行重建恢复操作,它属于read*方法的上层代码,一般控制了代码执行流程,并不提供和对象以及类相关的实质性代码;
readClassDescriptor
- protected ObjectStreamClass readClassDescriptor()
- throws IOException, ClassNotFoundException
- {
- ObjectStreamClass desc = new ObjectStreamClass();
- desc.readNonProxy(this);
- return desc;
- }
这个方法从序列化的字节流中读取类描述信息,它在重建Java对象时创建了一个新的ObjectStreamClass对象,然后调用该对象的readNonProxy方法作为默认实现,在子类重写该方法时,它的默认实现是传入了this参数给readNonProxy方法;
readTypeString
- String readTypeString() throws IOException {
- int oldHandle = passHandle;
- try {
- byte tc = bin.peekByte();
- switch (tc) {
- case TC_NULL:
- return (String) readNull();
- case TC_REFERENCE:
- return (String) readHandle(false);
- case TC_STRING:
- case TC_LONGSTRING:
- return readString(false);
- default:
- throw new StreamCorruptedException(
- String.format("invalid type code: %02X", tc));
- }
- } finally {
- passHandle = oldHandle;
- }
- }
该方法主要用于读取String对象,因为String在字节流中包含了三种读取方式:
- TC_NULL
如果读取了TC_NULL标记表示当前String对象是一个空引用; - TC_REFERENCE
如果读取了TC_REFERENCE标记表示读取的String对象是一个String的引用,这个对象之前已经在字节流中出现过; - TC_STRING 或 TC_LONGSTRING
如果读取了TC_STRING或TC_LONGSTRING标记表示读取的是一个String的对象,调用readString方法来完成反序列化操作;
readClassDesc
- private ObjectStreamClass readClassDesc(boolean unshared)
- throws IOException
- {
- byte tc = bin.peekByte();
- switch (tc) {
- case TC_NULL:
- return (ObjectStreamClass) readNull();
- case TC_REFERENCE:
- return (ObjectStreamClass) readHandle(unshared);
- case TC_PROXYCLASSDESC:
- return readProxyDesc(unshared);
- case TC_CLASSDESC:
- return readNonProxyDesc(unshared);
- default:
- throw new StreamCorruptedException(
- String.format("invalid type code: %02X", tc));
- }
- }
该方法主要用于判断当前读取的对象的类型:
- 如果读取的标记为TC_NULL则直接调用readNull方法;
- 如果读取的标记为TC_REFERENCE则调用readHandle方法;
- 如果读取的标记为TC_PROXYCLASSDESC则调用readProxyDesc方法返回动态代理类的类描述符;
- 如果读取的标记为TC_CLASSDESC则调用readNonProxyDesc方法返回反序列化类的描述符;
- 条件都不满足则抛出java.io.StreamCorruptedException异常;
13)read核心方法
readObject
- public final Object readObject()
- throws IOException, ClassNotFoundException
- {
- if (enableOverride) {
- return readObjectOverride();
- }
- // if nested read, passHandle contains handle of enclosing object
- int outerHandle = passHandle;
- try {
- Object obj = readObject0(false);
- 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();
- }
- }
- }
该方法为ObjectInputStream对外的API,但是它并不是核心方法,它只是用于判断应该调用readObjectOverride还是readObject0方法,在反序列执行完成过后,它会调用vlist成员的doCallbacks来执行完成过后的回调逻辑;
readObjectOverride
- protected Object readObjectOverride()
- throws IOException, ClassNotFoundException
- {
- return null;
- }
该方法的默认实现直接返回了null,这个方法存在的目的是为子类重写该方法。
readObject0
- 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;
- while ((tc = bin.peekByte()) == TC_RESET) {
- bin.readByte();
- handleReset();
- }
- depth++;
- 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));
- 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);
- }
- }
该方法在ObjectInputStream中才是readObject方法的核心方法,主要用于读取对象信息;它的详细过程如下:
- 先获取当前读取模式,检查是否是Data Block模式读取;
- 如果是Data Block模式,则先计算字节流中剩余的字节数量,剩余数量大于0或者defaultDataEnd的值为true【没有数据的情况】则抛出java.io.OptionalDataException异常信息——因为这个方法主要负责读取对象类型的数据,这些数据虽然本身是一个Data Block,但是在字节流中它并没有使用TC_BLOCKDATA标记,以及TC_BLOCKDATALONG标记开始,所以这个地方一旦发现还存在这两种类型的Data Block数据段则直接抛出异常;
计算完成过后关闭Data Block模式; - 从字节流中看是否可读取到TC_RESET标记,如果读取到了该标记则调用handleReset方法;
- 根据读取的标记执行反序列化运算:
如果读取TC_NULL——调用readNull函数;
如果读取TC_REFERENCE——调用readHandle函数;
如果读取TC_CLASS——调用readClass函数;
如果读取TC_CLASSDESC或TC_PROXYCLASSDESC——调用readClassDesc函数;
如果读取TC_STRING或TC_LONGSTRING——调用readString函数;
如果读取TC_ARRAY——调用readArray函数;
如果读取TC_ENUM——调用readEnum函数;
如果读取TC_OBJECT——调用readOrdinaryObject函数;
如果读取TC_EXCEPTION——调用readFatalExcception函数;
如果读取TC_BLOCKDATA或TC_BLOCKDATALONG——抛出异常信息,只是Data Block模式不同则抛出的异常信息不一样,开启Data Block模式;
如果读取TC_ENDBLOCKDATA——抛出异常信息,同上,只是不开启Data Block模式;
其他情况直接抛出异常信息;
- 需要注意的是在第四步中TC_ARRAY,TC_ENUM,TC_OBJECT,TC_STRING以及TC_LONGSTRING几种标记会对读取的返回结果调用checkResolve方法以检查反序列化的对象中是否重写了readResolve方法,如果重写了需要执行“Resolve”流程;
- 最后还原Data Block的原始模式,第一步读取的是什么值就还原成什么值;
*:这里的readObject0方法的详细流程读者可参考JVM的序列化规范中的readObject方法的概念描述来理解,这样就可以知道readObject方法究竟做了写什么事;
readStreamHeader
- 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));
- }
- }
这个方法很简单负责读取魔数和序列化版本信息;
readUnshared
- public Object readUnshared() throws IOException, ClassNotFoundException {
- // if nested read, passHandle contains handle of enclosing object
- int outerHandle = passHandle;
- try {
- Object obj = readObject0(true);
- 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();
- }
- }
- }
以“unshared”的方式从字节流中读取Java对象,直接调用readObject0方法重建Java对象,完成反序列化过后执行回调逻辑;
14)子类实现方法
resolveClass
- protected Class<?> resolveClass(ObjectStreamClass desc)
- throws IOException, ClassNotFoundException
- {
- String name = desc.getName();
- try {
- return Class.forName(name, false, latestUserDefinedLoader());
- } catch (ClassNotFoundException ex) {
- Class<?> cl = primClasses.get(name);
- if (cl != null) {
- return cl;
- } else {
- throw ex;
- }
- }
- }
resolveProxyClass
- protected Class<?> resolveProxyClass(String[] interfaces)
- throws IOException, ClassNotFoundException
- {
- ClassLoader latestLoader = latestUserDefinedLoader();
- ClassLoader nonPublicLoader = null;
- boolean hasNonPublicInterface = false;
- // define proxy in class loader of non-public interface(s), if any
- Class[] classObjs = new Class[interfaces.length];
- for (int i = 0; i < interfaces.length; i++) {
- Class cl = Class.forName(interfaces[i], false, latestLoader);
- if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
- if (hasNonPublicInterface) {
- if (nonPublicLoader != cl.getClassLoader()) {
- throw new IllegalAccessError(
- "conflicting non-public interface class loaders");
- }
- } else {
- nonPublicLoader = cl.getClassLoader();
- hasNonPublicInterface = true;
- }
- }
- classObjs[i] = cl;
- }
- try {
- return Proxy.getProxyClass(
- hasNonPublicInterface ? nonPublicLoader : latestLoader,
- classObjs);
- } catch (IllegalArgumentException e) {
- throw new ClassNotFoundException(null, e);
- }
- }
resolveObject
- protected Object resolveObject(Object obj) throws IOException {
- return obj;
- }
resolveClass、resolveProxyClass和resolveObject方法和ObjectOutputStream中定义的annotateClass、annotateProxyClass和replaceObject是对应的,JVM的序列化规范中有说明,如果ObjectOutputStream的子类重写了它的三个方法,则ObjectInputStream的子类也需要重写对应的方法。ObjectInputStream的resolveClass和resolveProxyClass中提供了默认实现——这是和序列化的方法唯一不同的地方;最后看看这三个方法的详细说明:
- resolveClass
该方法根据字节流中读取的类描述信息加载本地类,子类如果实现了这个方法则有可能读取源有所变化,默认实现是从字节流中读取,但重写了该方法可变换读取源;ObjectOutputStream中的annotateClass方法与之对应,和annotateClass方法一样该方法在字节流中针对每一个类的元数据信息只会调用唯一的一次,它虽然可以被子类重写和修改,但该方法的最终返回值必须是一个Class类型的对象;一旦返回过后,如果Class描述的对象不是一个array数组,则计算这个类中的serialVersionUID看是否匹配,不匹配则抛出java.lang.InvalidClassException异常;
这个方法的默认实现中第三个参数是ClassLoader,这个ClassLoader会执行下边的步骤:
a.如果栈顶发现的ClassLoader是用户自定义的一个ClassLoader,它会采取就近原则获取该方法帧数据中最近的ClassLoader;
b.否则直接返回null表示当前ClassLoader是一个空引用;
在调用Class.forName时若出现了ClassNotFoundException异常信息直接抛出该异常信息;如果传入的基础类型是java关键字或void,则这个Class会表示一个基础类型的数据,使用封装类型代替,若是void则直接返回。(如果resolve时发现int类型,则直接返回Integer.TYPE类型,否则直接抛出异常信息) - resolveProxyClass
该类主要处理动态代理接口,子类可重写用于解析动态代理数据信息,该类的默认实现这里不解析; - resolveObject
该类允许ObjectInputStream的可信任子类在反序列化过程中替换另外一个对象。默认情况下“Resolve”功能是关闭的,一旦调用了enableResolveObject方法就会启用“Resolve”功能,enableResolveObject方法在调用到时候会检查字节流中用于Resolve的对象是否是可信任的。每一个可序列化对象的引用都会传入resolveObject。为了确保对象私有状态不会暴露,只有可信任的字节流可使用resolveObject方法;
a.这个方法会在一个Java对象从字节流中读取过后,在该方法调用readObject方法之前调用,默认的resolveObject方法仅仅是返回了同样的对象;
b.如果子类正在替换对象,则它必须保证替代对象是和引用指向的对象兼容的。如果对象类型并不是字段的子类或者数组类的元素则中断反序列化过程抛出异常,这种情况下恢复Java对象失败;
c.这个方法仅仅在第一次读取同类型的Java对象时调用,之后读取到的该对象的引用则直接指向原始对象;
enableResolveObject
- protected boolean enableResolveObject(boolean enable)
- throws SecurityException
- {
- if (enable == enableResolve) {
- return enable;
- }
- if (enable) {
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- sm.checkPermission(SUBSTITUTION_PERMISSION);
- }
- }
- enableResolve = enable;
- return !enableResolve;
- }
该方法会启用反序列化过程中的“Resolve”功能
- 如果该功能启用,并且传入参数为true,则什么都不做直接返回;
- 如果想要启用“Resolve”功能,则需要调用Java的安全管理器并检查代码执行权限SUBSTITUTION_PERMISSION;
15)构造方法
该类的构造方法如下:
- public ObjectInputStream(InputStream in) throws IOException {
- verifySubclass();
- bin = new BlockDataInputStream(in);
- handles = new HandleTable(10);
- vlist = new ValidationList();
- enableOverride = false;
- readStreamHeader();
- bin.setBlockDataMode(true);
- }
- protected ObjectInputStream() throws IOException, SecurityException {
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
- }
- bin = null;
- handles = null;
- vlist = null;
- enableOverride = true;
- }
protected构造函数:
- 调用安全管理器SecurityManager,并且使用安全管理器检查权限SUBCLASS_IMPLEMENTATION_PERMISSION;
- 设置成员属性的默认值:
bin——null、handles——null、vlist——null、enableOverride——true
public构造函数:
- 调用verifySubclass方法验证子类信息;
- 初始化bin成员,实例化一个BlockDataInputStream;
- 初始化handles和vlist,创建两个对应的实例;
- 将enableOverride的值设成false;
- 调用readStreamHeader方法读取魔数和序列化版本信息;
- 开启Data Block模式读取信息;
到这里ObjectInputStream中的大部分方法都已经解析完成了,其他的辅助方法和相关成员函数这里就不解析了,下一个章节将提供一些例子来解析Java序列化中的面向对象的特性;第一章解析了Java序列化生成二进制文件的文件结构,从文件结构的角度剖析了Java序列化的相关原理和算法;后边的章节将根据源代码中的代码执行流程来分析Java序列化如何来生成对应的二进制文件,从另外的一个角度再来回顾Java序列化的相关原理和算法。
5.序列化原理和算法——面向对象
本章会提供大量的实例来解析序列化中的一些面向对象的特性,尽可能覆盖一些常见的序列化问题包括一些比较争议的概念,本章节的解析主要不会针对生成的二进制文件的结构,仅仅根据Java语言特性来分析二进制文件生成过程以及讨论一些和面向对象相关的问题。
i.数组的序列化分析
先看个例子:
- package org.susan.java.serial;
- import java.io.FileOutputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class ArraySerial implements Serializable{
- /**
- *
- */
- private static final long serialVersionUID = 749500769727730567L;
- private String name;
- public ArraySerial(String name,int age){
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- private int age;
- // 运行函数
- public static void main(String args[]) throws Exception{
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("array.obj"));
- // 序列化普通数组
- short[] arr = new short[4];
- for( int i = 0; i < 4; i++ ){
- short ele = (short) (Math.random() * 100);
- arr[i] = ele;
- }
- out.writeObject(arr);
- // 序列化对象数组
- ArraySerial[] objArr = new ArraySerial[4];
- for( int i = 0; i < 4; i++ ){
- objArr[i] = new ArraySerial("LangYu"+i,(int) (Math.random() * 60));
- }
- out.writeObject(objArr);
- out.flush();
- out.close();
- }
- }
上述示例中创建了两个数组:一个基础类型的数组,一个对象类型的数组,随后调用writeObject方法将这些数据写入到目标介质文件中,分析这个示例之前,我们先根据代码的流程一步一步解析它会生成什么样的二进制文件。注意括号内的内容,括号内注明了截取的代码的具体位置,带【】符号的步骤表示会生成二进制序列的步骤!
- 第一步实例化一个ObjectOutputStream,在实例化时调用了它的单参数构造函数,在调用该构造函数时会优先调用verifySubclass方法。该方法执行时会在第一个return处直接返回,因为该方法中调用getClass方法时返回的Class类型就为ObjectOutputStream;
(ObjectOutputStream--> verifySubclass)
- Class cl = getClass();
- if (cl == ObjectOutputStream.class) {
- return;
- }
- verifySubclass验证完过后会初始化成员属性:bout、handles、subs、enableOverride;
(ObjectOutputStream --> <Constructor(arg)>)
- bout = new BlockDataOutputStream(out);
- handles = new HandleTable(10, (float) 3.00);
- subs = new ReplaceTable(10, (float) 3.00);
- enableOverride = false;
- 【AC ED 00 05】随后会调用writeStreamHeader方法,这个方法会往缓冲区中写入两个特殊标记STREAM_MAGIC、STREAM_VERSION;
(ObjectOutputStream --> writeStreamHeader)
- bout.writeShort(STREAM_MAGIC);
- bout.writeShort(STREAM_VERSION);
- 开启输出流的Data Block模式;
(ObjectOutputStream --> <Constructor(arg)>)
- bout.setBlockDataMode(true);
- 因为extendedDebugInfo成员属性为false,所以并未开启Debug模式;
- 调用ObjectOutputStream的公共API成员函数writeObject,writeObject会先判断enableOverride成员属性的值,该值在第二步已经设置成false了;
(ObjectOutputStream --> writeObject)
- if (enableOverride) {
- writeObjectOverride(obj);
- return;
- }
- 因为enableOverride成员属性值为false,所以直接调用writeObject0成员函数,注意传入这个函数的第二个参数;在代码中第二个参数传入了false,证明使用的Java对象序列化方式不是“unshared”的方式;
(ObjectOutputStream --> writeObject)
- writeObject0(obj, false);
- 因为写入的是新对象,所以它不满足代码写入第一块的内容:1)写入引用并非null引用;2)写入的对象在原始字节流中并未出现过;3)写入的对象不是特殊对象类型【Class或ObjectStreamClass】;
(ObjectOutputStream --> writeObject0)
- 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;
- }
- 因为写入的是array对象,它的内部并没有重写writeReplace方法,所以直接跳过writeObject0方法中的enableReplace检测,同样不执行针对“替换对象”的字节写入流程;
- 除了上述的类型判断以外,接下来进入针对Array对象的写入过程,进入下边代码的第二个分支:
(ObjectOutputStream --> writeObject0)
- if (obj instanceof String) {
- writeString((String) obj, unshared);
- } else if (cl.isArray()) {
- writeArray(obj, desc, unshared);
- } else if (obj instanceof Enum) {
- writeEnum((Enum) obj, desc, unshared);
- } else if (obj instanceof Serializable) {
- writeOrdinaryObject(obj, desc, unshared);
- } else {
- if (extendedDebugInfo) {
- throw new NotSerializableException(cl.getName() + "\n"
- + debugInfoStack.toString());
- } else {
- throw new NotSerializableException(cl.getName());
- }
- }
- 【75】调用writeArray方法写入array对象二进制序列段数据,writeArray有三个形参:
(ObjectOutputStream --> writeArray)
array——java.lang.Object:传入的对象本身的信息;
desc——java.io.ObjectStreamClass:传入的对象所属类的元数据信息;
unshared——boolean:传入对象序列化的方式,从外层writeObject方法中可以知道这个值是false的;
先调用bout成员函数的writeBytes方法写入TC_ARRAY标记,随后调用writeClassDesc方法写入array对象所属类元数据信息;
- bout.writeByte(TC_ARRAY);
- writeClassDesc(desc, false);
- writeClassDesc方法有四个分支,这个四个分支代码这里不枚举,实际上在写入array对象所属类的元数据时,会调用最后一个分支的writeNonProxyDesc方法,只是需要注意的是在调用writeClassDesc方法时第二个参数同样传入的是一个固定的false,并没将上层调用中的“unshared”方式的值传入这里;
- 【72】writeNonProxyDesc方法会先写入TC_CLASSDESC标记,随后将该类元数据的引用Handle插入到handles成员属性中:
(ObjectOutputStream --> writeNonProxyDesc)
- bout.writeByte(TC_CLASSDESC);
- handles.assign(unshared ? null : desc);
- 判断序列化流协议的版本,不同的版本需要调用不同的方法,如果是PROTOCOL_VERSION_1协议,调用writeNonProxy方法,如果不是PROTOCOL_VERSION_1协议,则调用writeClassDescriptor方法;
(ObjectOutputStream --> writeNonProxyDesc)
- if (protocol == PROTOCOL_VERSION_1) {
- // do not invoke class descriptor write hook with old protocol
- desc.writeNonProxy(this);
- } else {
- writeClassDescriptor(desc);
- }
- 因为示例中使用的是JDK 1.5以上的JDK环境,所以使用的是非PROTOCOL_VERSION_1协议,直接调用writeClassDescriptor方法,调用时使用该方法的默认实现;
(ObjectOutputStream --> writeClassDescriptor)
- desc.writeNonProxy(this);
- 【00 02 5B 53 EF 83 2E 06 E5 5D B0 FA】写入array数组的名称和serialVersionUID的值,需要注意的是数组的类的serialVersionUID的值是调用computeDefaultSUID方法计算的,因为类型是short[],所以类名应该是“[S【5B 53】”,SUID的计算细节就不在这里重复了,读者可以根据SUID的算法自己去实践实践,计算出来的值是8个字节的long类型数据,为EF 83 2E 06 E5 5D B0 FA;可以读者一定有个疑问,这一段序列的前缀【00 02】是怎么产生的?不知读者是否记得在写入TC_CLASSDESC过后,会先写入类型名称的长度,再写入类型名称的内容——writeUTF函数会处理这个细节,00 02就是short整数类型的值(“[S”.length() ==2)。注意:这里写入的“[S”不是签名信息,而是类名信息,请读者区分类名和签名的概念;
(ObjectStreamClass --> writeNonProxy)
- out.writeUTF(name);
- out.writeLong(getSerialVersionUID());
- 为了不让读者困惑,这个地方再看看writeUTF成员函数的定义,这样就彻底理解00 02为什么会出现在这个地方了:
(ObjectOutputStream.BlockDataOutputStream--> writeUTF)
- void writeUTF(String s, long utflen) throws IOException {
- if (utflen > 0xFFFFL) {
- throw new UTFDataFormatException();
- }
- writeShort((int) utflen);
- if (utflen == (long) s.length()) {
- writeBytes(s);
- } else {
- writeUTFBody(s);
- }
- }
- 判断当前数组对应的SC_*标记,因为Java中的数组都是默认实现了Serializable接口,所以flag标记的值为ObjectStreamConstants.SC_SERIALIZABLE;
(ObjectStreamClass--> writeNonProxy)
- flags |= ObjectStreamConstants.SC_SERIALIZABLE;
- 【02】写入SC_SERIALIZABLE标记值,前文解析了过了02的含义,但是里面只是说明了02表示对象所属类是“实现了Serializable接口可支持序列化的”,那么从这里可以知道为什么是02的值了;
(ObjectStreamClass --> writeNonProxy)
- out.writeByte(flags);
- 【00 00】写入short[]类型中成员属性的数量信息,因为short[]的成员属性的数量为0,所以这里写入的是一个short类型的值,为00 00;
(ObjectStreamClass --> writeNonProxy)
- out.writeShort(fields.length);
- 因为当前这个数组中没有任何成员函数信息,所以针对每个成员函数就到此为止了,写入了当前类的元数据信息过后,会在Data Block模式下调用annotateClass方法;
(ObjectOutputStream --> writeNonProxyDesc)
- Class cl = desc.forClass();
- bout.setBlockDataMode(true);
- annotateClass(cl);
- bout.setBlockDataMode(false);
- 【78】因为当前的类描述信息已经写入完毕,所以在最后写入TC_ENDBLOCKDATA标记;
(ObjectOutputStream --> writeNonProxyDesc)
- bout.writeByte(TC_ENDBLOCKDATA);
- 在TC_ENDBLOCKDATA标记写入过后,再调用writeClassDesc写入当前Java对象的父类信息,注意2点:
1)writeNonProxyDesc调用了writeClassDesc方法,而writeClassDesc的判断条件中也会调用writeNonProxyDesc方法,这个地方是一个循环调用,使得系统可实现“从下至上递归遍历”;
2)当一个类的直接父类为Object时,getSuperDesc成员函数调用会返回null;
(ObjectOutputStream --> writeNonProxyDesc)
- writeClassDesc(desc.getSuperDesc(), false);
- 【70】在writeClassDesc中调用writeNull成员函数,这个函数会写入TC_NULL标记;
(ObjectOutputStream --> writeNull)
- bout.writeByte(TC_NULL);
- 接下来的代码段会写入该数组中的数据,先看看代码中这个数组的信息,类型为short[],长度为4,它的元素类型为基本类型(通过getComponentType获取元素的数据类型);
(ObjectOutputStream--> writeArray)
- Class ccl = desc.forClass().getComponentType();
- if (ccl.isPrimitive()) {
- // 如果元素是基础类型的代码段
- ...
- } else {
- // 如果元素是对象类型的代码段
- ...
- }
- 【00 00 00 04 00 26 00 5D 00 58 00 31】因为数组的元素是short类型,所以ccl.isPrimitive方法会返回true,随后调用bout成员函数的writeInt方法写入数组长度,在调用writeShorts方法直接将该数组中的每一个元素写入字节流;
注:这段序列的最后8个字节表示4个short值,因为每一个值调用了(short) (Math.random() * 100)方法获得,所以这8个字节表示4个随机数——所以这8个字节每一次运行时结果都不一样;
(ObjectOutputStream --> writeArray)
- short[] sa = (short[]) array;
- bout.writeInt(sa.length);
- bout.writeShorts(sa, 0, sa.length);
- 在代码最后会还原Data Block模式,并且修改depth成员属性的值;
(ObjectOutputStream --> writeObject0)
- } finally {
- depth--;
- bout.setBlockDataMode(oldMode);
- }
- 到这里主代码中out.writeObject(arr)就执行完毕了,接下来看看【对象数组】的代码流程,结合上边的【6~27】步看看两种数组的二进制序列生成的差异,前边相同的部分这里不再解析;
【75 72】写入的TC_ARRAY、TC_CLASSDESC两个标记;
【00 24】写入数组的名称长度,名称的内容为:“[Lorg.susan.java.serial.ArraySerial;”,这个字符串长度为36,换算成十六进制为24;
【5B 4C 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61 6C 3B】字符串“[Lorg.susan.java.serial.ArraySerial;”;
【98 52 3B E3 AD 37 EE 9D】当前这个数组的serialVersionUID的值;
【02】写入的标记SC_SERIALIZABLE的值;
【00 00】表示当前这个对象数组中没有任何成员属性,所以成员属性的数量为0;
【78 70】表示标记TC_ENDBLOCKDATA、TC_NULL;
到这里对象数组的类描述信息【元数据信息】就执行完成了,和上边的步骤【11~24】的代码是一模一样的;
- 【00 00 00 04】对象数组的复杂在于针对每一个元素的写入过程,也就是从上边第25步开始有变化,调用ccl.isPrimitive方法会返回false,走入另外一个代码流程,先写入对象数组的长度信息,示例中数组长度为4;
(ObjectOutputStream --> writeArray)
- Object[] objs = (Object[]) array;
- int len = objs.length;
- bout.writeInt(len);
- 写入对象数组的长度信息过后,接着写入每一个元素的信息,因为元素的类型是对象类型,所以调用writeObject0方法,注意这部分信息的调用可开启Debug调试模式,代码中会针对extendedDebugInfo成员属性进行检测;
(ObjectOutputStream --> writeArray)
- for (int i = 0; i < len; i++) {
- if (extendedDebugInfo) {
- debugInfoStack.push("element of array (index: " + i + ")");
- }
- try {
- writeObject0(objs[i], false);
- } finally {
- if (extendedDebugInfo) {
- debugInfoStack.pop();
- }
- }
- }
- writeObject0方法的主要流程这里就不阐述了,这里调用的是writeOrdinaryObject方法;
(ObjectOutputStream --> writeObject0)
- writeOrdinaryObject(obj, desc, unshared);
- 在调用该方法时,系统会先调用ObjectStreamClass类的checkSerialize方法检查数组中的元素是否可支持序列化,这里检查的类为org.susan.java.serial.ArraySerial,如果该类不支持序列化,checkSerialize方法会抛出异常信息;
(ObjectOutputStream --> writeOrdinaryObject)
- desc.checkSerialize();
- 【73】在写入数组元素时候先写入TC_OBJECT标记信息,跟随其后调用writeClassDesc方法写入该对象所属类的元数据信息,该方法内部又调用了writeNonProxyDesc方法;
(ObjectOutputStream --> writeOrdinaryObject)
- bout.writeByte(TC_OBJECT);
- writeClassDesc(desc, false);
- 【72】调用writeNonProxyDesc方法写入TC_CLASSDESC标记,随后调用writeClassDescriptor方法写入对应的类描述信息;
(ObjectOutputStream --> writeNonProxyDesc)
- bout.writeByte(TC_CLASSDESC);
- handles.assign(unshared ? null : desc);
- 【00 21 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61 6C】在写入类描述信息时会先写入类名,类名写入调用了writeUTF方法,其格式为“长度 名称”,这段代码生成的二进制序列如下:
00 21段:类名长度为33,十六进制值为21,之后所有生成的二进制的值为字符串:“org.susan.java.serial.ArraySerial”;
(ObjectStreamClass --> writeNonProxy)
- out.writeUTF(name);
- out.writeLong(getSerialVersionUID());
- 【0A 66 C2 FA A2 80 D3 87】随后写入SUID的信息(见第35步),该serialVersionUID和上边写入SUID的值不一样,这个值是直接提取的源代码中定义的值749500769727730567;
- 【02】随后写入SC_SERIALIZABLE标记信息;
- 从这一步开始,和上边写入数组的描述信息就有所不同了,仔细看看这段写入字段信息的代码,这段代码会先写入该类中的成员属性的数量:
【00 02】该类中包含了两个字段name和age,所以这里写入的二进制数据是00 02;
【49 00 03 61 67 65】49为类型代码,值为“I”,00 03表示属性age的名称长度,61 67 65表示属性名称字符串“age”;
【4C 00 04 6E 61 6D 65】4C为类型代码,值为“L”,00 04表示属性name的名称长度,6E 61 6D 65表示属性名称字符串“name”;
注意该类中存在一个判断,在判断内部会调用writeTypeString方法,该方法会根据传入的String类型分别调用三个不同函数:
null——调用writeNull
引用Handle——调用writeHandle
String对象——调用writeString
【74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B】因为这里是第一次遇到String类型的对象,所以这里会先写入String的元数据信息,74表示TC_STRING标记,00 12表示类型名的长度,最后18个字节表示类型字符串“Ljava/lang/String;”;
(ObjectStreamClass --> writeNonProxy)
- 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());
- }
- }
- 【78 70】最后写入TC_ENDBLOCKDATA标记和TC_NULL标记,表示String类的元数据结束;
- 在完成元数据的写入信息过后,需要判断序列化的类是“默认序列化”还是“外部化”,如果是“外部化”调用writeExternalData方法,如果是“默认序列化”调用writeSerialData方法;
(ObjectOutputStream --> writeOrdinaryObject)
- handles.assign(unshared ? null : obj);
- if (desc.isExternalizable() && !desc.isProxy()) {
- writeExternalData((Externalizable) obj);
- } else {
- writeSerialData(obj, desc);
- }
- 在调用writeSerialData方法时会先调用默认序列化字段数据的方法defaultWriteFields;
(ObjectOutputStream --> writeOrdinaryObject)
- defaultWriteFields(obj, slotDesc);
- 在调用defaultWriteFields方法之前,会先调用ObjectStreamClass类的checkDefaultSerialize方法检查该对象所属类是否支持默认序列化机制;
(ObjectOutputStream --> defaultWriteFields)
- desc.checkDefaultSerialize();
- 【00 00 00 2F】先写入age属性的值,这个值也是60之内的一个随机数,每次运行的时候不一样;
(ObjectOutputStream --> writeOrdinaryObject)
- int primDataSize = desc.getPrimDataSize();
- if (primVals == null || primVals.length < primDataSize) {
- primVals = new byte[primDataSize];
- }
- desc.getPrimFieldValues(obj, primVals);
- bout.write(primVals, 0, primDataSize, false);
- 【74 00 07 4C 61 6E 67 59 75 30】随后调用writeObject0方法写入name属性的值,这样对象数组的第一个元素就完成了写入操作;
- 【73 71 00 7E 00 04】这段序列是数组元素中第二个元素的元数据信息,和第一个元素不一样,第一个元素写入的是TC_OBJECT标记,第二个元素写入的就是TC_REFERENCE标记了;
(ObjectOutputStream --> writeHandle)
- bout.writeByte(TC_REFERENCE);
- bout.writeInt(baseWireHandle + handle);
- 接下来就直接看看最后的几段序列的生成:
【00 00 00 26 74 00 07 4C 61 6E 67 59 75 31】第二个元素age和name的值;
【73 71 00 7E 00 04】第三个元素的引用Handle;
【00 00 00 1C 74 00 07 4C 61 6E 67 59 75 32】第三个元素age和name的值;
【73 71 00 7E 00 04】第四个元素的引用Handle;
【00 00 00 26 74 00 07 4C 61 6E 67 59 75 33】第四个元素age和name的值; - 输出了上述的序列过后整个二进制序列的生成过程就结束了,而之后的代码这儿就不详细解析了;
上边的流程已经非常详细地解析了代码中生成二进制序列的步骤,最后看看整个二进制序列:
AC ED 00 05 75 72 00 02 5B 53 EF 83 2E 06 E5 5D
B0 FA 02 00 00 78 70 00 00 00 04 00 26 00 5D 00
58 00 31 75 72 00 24 5B 4C 6F 72 67 2E 73 75 73
61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41
72 72 61 79 53 65 72 69 61 6C 3B 98 52 3B E3 AD
37 EE 9D 02 00 00 78 70 00 00 00 04 73 72 00 21
6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73
65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61
6C 0A 66 C2 FA A2 80 D3 87 02 00 02 49 00 03 61
67 65 4C 00 04 6E 61 6D 65 74 00 12 4C 6A 61 76
61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 78 70
00 00 00 2F 74 00 07 4C 61 6E 67 59 75 30 73 71
00 7E 00 04 00 00 00 26 74 00 07 4C 61 6E 67 59
75 31 73 71 00 7E 00 04 00 00 00 1C 74 00 07 4C
61 6E 67 59 75 32 73 71 00 7E 00 04 00 00 00 26
74 00 07 4C 61 6E 67 59 75 33
B0 FA 02 00 00 78 70 00 00 00 04 00 26 00 5D 00
58 00 31 75 72 00 24 5B 4C 6F 72 67 2E 73 75 73
61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 41
72 72 61 79 53 65 72 69 61 6C 3B 98 52 3B E3 AD
37 EE 9D 02 00 00 78 70 00 00 00 04 73 72 00 21
6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73
65 72 69 61 6C 2E 41 72 72 61 79 53 65 72 69 61
6C 0A 66 C2 FA A2 80 D3 87 02 00 02 49 00 03 61
67 65 4C 00 04 6E 61 6D 65 74 00 12 4C 6A 61 76
61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 78 70
00 00 00 2F 74 00 07 4C 61 6E 67 59 75 30 73 71
00 7E 00 04 00 00 00 26 74 00 07 4C 61 6E 67 59
75 31 73 71 00 7E 00 04 00 00 00 1C 74 00 07 4C
61 6E 67 59 75 32 73 71 00 7E 00 04 00 00 00 26
74 00 07 4C 61 6E 67 59 75 33
上边的二进制序列是不是和我们步骤中的解析一模一样呢?简单总结一下这个解析的几个注意点:
- 这段序列中存在随机数,所以读者在运行过后生成的序列可能会和上边列出来的序列有所差异;
- 注意代码中的调试段,调试段的内容只有在序列化的时候存在,反序列化时没有提供调试部分的代码;
- 这里的代码流程解析只是解析了序列化的过程,并没有解析反序列化的数据读取过程;
ii.非序列化超类的序列化
这个章节开始就不会再去分析二进制文件的内容了,只会在必要的时候插入几段二进制序列代码来辅助分析过程;
如果一个可序列化的子类的直接子类既不是Object,而它的父类又不可序列化,那么会出现什么情况,先看看例子:
- package org.susan.java.serial;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- // import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class CaseOne {
- // 执行函数
- public static void main(String args[]) throws Exception{
- // 序列化
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("caseone.obj"));
- SubClass sub = new SubClass(true,"silentbalanceyh","Yu Lang");
- out.writeObject(sub);
- out.flush();
- out.close();
- // 反序列化
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("caseone.obj"));
- SubClass subNew = (SubClass)in.readObject();
- in.close();
- System.out.println("SUB:" + subNew);
- System.out.println("SUPER:" + subNew.getSuper());
- }
- }
- class SubClass extends BaseClass implements Serializable{
- private static final long serialVersionUID = 7442418880476300463L;
- private boolean gender;
- private String name;
- private String author;
- public SubClass(){
- super();
- }
- public SubClass(boolean gender,String name, String author){
- super(name,27);
- this.gender = gender;
- this.name = name;
- this.author = author;
- }
- public String getSuper(){
- return super.toString();
- }
- /* private void writeObject(ObjectOutputStream out) throws IOException{
- out.defaultWriteObject();
- out.writeObject(super.name);
- out.writeInt(age);
- }
- private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
- in.defaultReadObject();
- super.name = (String)in.readObject();
- age = (int)in.readInt();
- }*/
- @Override
- public String toString() {
- return "SubClass [gender=" + gender + ", name=" + name + ", author="
- + author + "]";
- }
- }
- class BaseClass{
- @Override
- public String toString() {
- return "BaseClass [name=" + name + ", age=" + age + "]";
- }
- protected String name;
- protected int age;
- public BaseClass(){
- }
- public BaseClass(String name, int age){
- this.name = name;
- this.age = age;
- }
- }
看看上边代码的输出:
SUB:SubClass [gender=true, name=silentbalanceyh, author=Yu Lang]
SUPER:BaseClass [name=null, age=0]
SUPER:BaseClass [name=null, age=0]
从上边的输出可以知道,如果一个可序列化的类的父类是不可序列化的,则在默认序列化过程父类会直接被忽略掉,如果需要序列化该父类的数据,只有在该类中重写writeObject方法和readObject方法来实现。但是有一点注意:如果父类和子类的成员属性同名,在writeObject以及readObject两个方法中,针对成员函数的调用最后显示使用this或super来进行。去掉上边例子中的注释过后其输出为:
SUB:SubClass [gender=true, name=silentbalanceyh, author=Yu Lang]
SUPER:BaseClass [name=silentbalanceyh, age=27]
SUPER:BaseClass [name=silentbalanceyh, age=27]
如果上边的代码不使用super关键字,则BaseClass的name属性就会为null值;
iii.关于构造函数的调用
前文已经讲解了序列化的大部分的内容,这里再看个例子:
- package org.susan.java.serial;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class CaseTwo implements Serializable{
- private static final long serialVersionUID = -5719199880473656625L;
- public CaseTwo(){
- System.out.println("Called Constructor!");
- }
- public static void main(String args[]) throws Exception{
- // 序列化
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("casetwo.obj"));
- CaseTwo sub = new CaseTwo();
- out.writeObject(sub);
- out.flush();
- out.close();
- // 反序列化
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("casetwo.obj"));
- @SuppressWarnings("unused")
- CaseTwo subNew = (CaseTwo)in.readObject();
- in.close();
- }
- }
这个例子的输出为:
Called Constructor
需要说明的内容很简单:序列化机制在反序列对象的过程中并未调用构造函数,而是直接恢复的Java的状态数据信息;
iv.抽象类&接口
前文中一直没有提到抽象类和接口的字节结构,这里再看一个和它相关的例子:
- package org.susan.java.serial;
- import java.io.FileOutputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class CaseThree {
- public static void main(String args[]) throws Exception {
- // 序列化
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
- "casethree.obj"));
- BaseClass1 base = new BaseClass1();
- out.writeObject(base);
- out.flush();
- out.close();
- }
- }
- class BaseClass1 extends AClass1 implements AInterface,Serializable {
- private static final long serialVersionUID = 3530141157381778908L;
- }
- abstract class AClass1 implements CInterface {
- }
- interface AInterface extends BInterface {
- int name = 0;
- }
- interface BInterface {
- String bName = "Lang Yu";
- }
- interface CInterface {
- }
上述例子的对象结构如下:
分析一下这段代码输出的二进制序列文件的详细内容:
- 【AC ED 00 05】魔数和序列化版本信息;
- 【73 72】写入TC_OBJECT和TC_CLASSDESC标记;
- 【00 20 6F 72 67 2E 73 75 73 61 6E 2E 6A 61 76 61 2E 73 65 72 69 61 6C 2E 42 61 73 65 43 6C 61 73 73 31】类名的长度以及类名字符串:“org.susan.java.serial.BaseClass1”;
- 【30 FD 94 AD DC BA 2D DC】对应类的SUID的值;
- 【00 00】表示这个类没有任何属性,属性数量为0;
- 【78 70】表示TC_ENDBLOCKDATA和TC_NULL标记信息;
读者一定有困惑,为什么这么复杂的结构二进制序列和普通的Object写入没有区别呢?确实如此,这里使用的新建对象的方法是BaseClass1 base = new BaseClass1();,即使这行代码像下边这种其二进制序列输出也一样。
- AInterface base = new BaseClass1();
- CInterface base = new BaseClass1();
- BInterface base = new BaseClass1();
- AClass1 base = new BaseClass1();
上边的初始化方式生成的二进制序列是一模一样的,那么接口中的name,bName哪儿去了?——相信读者还记得接口常量【《Java中的类和对象》】,这里就不详讲了。
6.本章小结
到这里基本涉及到Java序列化的整体知识就已经讲完了,其实Java源码分析占用了两个章节的内容,提供这么多详细的解析是希望读者从不同的角度去彻底理解Java序列化部分的详细内容,如果有疑问可以发送邮件到:silentbalanceyh@126.com,但是这里我不承诺能够全部回复。当然这个Blog我会尽可能不让它荒废掉,每隔一段时间我尽量发布一些比较高质量的文章在这里,希望读者理解一点的就是:我的Blog中的文章一般篇幅比较长,一篇文章可能在3~5万字之间,而每一部分Java知识点的话可能在8到10W字左右,所以没有办法“高产”,望谅解。