java对序列化serialVersionUID的处理分析
继上次分析了java的序列化过程之后,对于serialVersionUID的处理还不是很清晰,今天再看下代码,对serialVersionUID的处理进行了了解,上次的序列化过程分析可以参考另外一篇文章:http://zhwj184.iteye.com/blog/1550699
ObjectOutputStream.java调用writeObject的时候会调用到下面的代码:
这是调用ObjectStreamClass.java的writeNonProxy方法,写入非代理类的元数据信息
在写入类的元数据的时候会把serialVersionUID写入:
/** * Writes non-proxy class descriptor information to given output stream. */ void writeNonProxy(ObjectOutputStream out) throws IOException { out.writeUTF(name); out.writeLong(getSerialVersionUID()); byte flags = 0; if (externalizable) { flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; int protocol = out.getProtocolVersion(); if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { flags |= ObjectStreamConstants.SC_BLOCK_DATA; } } else if (serializable) { flags |= ObjectStreamConstants.SC_SERIALIZABLE; } if (hasWriteObjectData) { flags |= ObjectStreamConstants.SC_WRITE_METHOD; } if (isEnum) { flags |= ObjectStreamConstants.SC_ENUM; } out.writeByte(flags); 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()); } } }
在读取的时候会做几个部分的校验:
在ObjectInputStream.java的readObject方法时调用ObjectStreamClass.java的readNonProxy方法,
这里会读取suid = Long.valueOf(in.readLong());就是读取serialVersionUID,然后先做第一步校验if (isEnum && suid.longValue() != 0L) 如果是枚举类则serialVersionUID为0.
void readNonProxy(ObjectInputStream in) throws IOException, ClassNotFoundException { name = in.readUTF(); suid = Long.valueOf(in.readLong()); isProxy = false; byte flags = in.readByte(); hasWriteObjectData = ((flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0); hasBlockExternalData = ((flags & ObjectStreamConstants.SC_BLOCK_DATA) != 0); externalizable = ((flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0); boolean sflag = ((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++) { 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(); }
然后下面还会对serialVersionUID进行下一步的验证:同样是在ObjectStreamClass.java的initNonProxy方法,suid = Long.valueOf(model.getSerialVersionUID());去除序列化后的model对象的serialVersionUID,if (serializable == localDesc.serializable &&!cl.isArray() &&suid.longValue() != localDesc.getSerialVersionUID()) 然后判断是否跟本地的class对象是否都是继承serializable 接口,并且cl不是数组,且serialVersionUID要跟当前序列化的class对象的serialVersionUID一致。
/** * Initializes class descriptor representing a non-proxy class. */ void initNonProxy(ObjectStreamClass model, Class cl, ClassNotFoundException resolveEx, ObjectStreamClass superDesc) throws InvalidClassException { this.cl = cl; this.resolveEx = resolveEx; this.superDesc = superDesc; name = model.name; suid = Long.valueOf(model.getSerialVersionUID()); 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 (cl != null) { localDesc = lookup(cl, true); if (localDesc.isProxy) { throw new InvalidClassException( "cannot bind non-proxy descriptor to a proxy class"); } if (isEnum != localDesc.isEnum) { throw new InvalidClassException(isEnum ? "cannot bind enum descriptor to a non-enum class" : "cannot bind non-enum descriptor to an enum class"); } if (serializable == localDesc.serializable && !cl.isArray() && suid.longValue() != localDesc.getSerialVersionUID()) { throw new InvalidClassException(localDesc.name, "local class incompatible: " + "stream classdesc serialVersionUID = " + suid + ", local class serialVersionUID = " + localDesc.getSerialVersionUID()); } if (!classNamesEqual(name, localDesc.name)) { throw new InvalidClassException(localDesc.name, "local class name incompatible with stream class " + "name \"" + name + "\""); } if (!isEnum) { if ((serializable == localDesc.serializable) && (externalizable != localDesc.externalizable)) { throw new InvalidClassException(localDesc.name, "Serializable incompatible with Externalizable"); } if ((serializable != localDesc.serializable) || (externalizable != localDesc.externalizable) || !(serializable || externalizable)) { deserializeEx = new InvalidClassException(localDesc.name, "class invalid for deserialization"); } } cons = localDesc.cons; writeObjectMethod = localDesc.writeObjectMethod; readObjectMethod = localDesc.readObjectMethod; readObjectNoDataMethod = localDesc.readObjectNoDataMethod; writeReplaceMethod = localDesc.writeReplaceMethod; readResolveMethod = localDesc.readResolveMethod; if (deserializeEx == null) { deserializeEx = localDesc.deserializeEx; } } fieldRefl = getReflector(fields, localDesc); // reassign to matched fields so as to reflect local unshared settings fields = fieldRefl.getFields(); }
所以serialVersionUID在前后都不能做修改,否则会导致序列化失败。