zoukankan      html  css  js  c++  java
  • 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在前后都不能做修改,否则会导致序列化失败。

  • 相关阅读:
    LCA --算法竞赛专题解析(29)
    倍增与ST算法 --算法竞赛专题解析(28)
    可持久化线段树(主席树) --算法竞赛专题解析(27)
    莫队算法 --算法竞赛专题解析(26)
    分块 --算法竞赛专题解析(25)
    表格标题或内容平铺样式
    SpringMVC传参
    按字节截取字符串
    Redis常用命令及知识
    修改数据库字段类型或名字
  • 原文地址:https://www.cnblogs.com/secbook/p/2655148.html
Copyright © 2011-2022 走看看