zoukankan      html  css  js  c++  java
  • Java 序列化和反序列化(三)Serializable 源码分析

    Java 序列化和反序列化(三)Serializable 源码分析 - 2

    在上一篇文章中围绕 ObjectOutputStream#writeObject 讲解了一下序列化的整个流程,这中间很多地方涉及到了 ObjectStreamClass 和 ObjectStreamField 这两个类。

    • ObjectStreamField 按官方的说法是是字段的序列化描述符,本质是对 Field 字段的包装,包括字段名、字段值等。可以通过 ObjectStreamClass#getFields 获取所有需要序列化的字段信息。

    • ObjectStreamClass 按官方的说法是类的序列化描述符,本质是对 Class 类的包装,提取了序列化时类的一些信息,包括字段的描述信息和 serialVersionUID。可以使用 lookup 方法找到/创建在此 Java VM 中加载的具体类的 ObjectStreamClass。

    1. ObjectStreamField

    ObjectStreamField 只是一个简单的 JavaBean,保存了序列化过程中字段的元数据信息,包括字段的类型、类型代码、签名等。 可以通过 ObjectStreamClass#getFields 获取所有需要序列化的字段信息。

    1.1 数据结构

    
    private final String name;      // 1. field name
    private final String signature; // 2. canonical JVM signature of field type
    private final Class<?> type;    // 3. 字段类型
    private final boolean unshared; // 4. 序列化时字段是否是 unshared
    private final Field field;      // 5. Field
    private int offset = 0;         // 6. 序列化时数据在 buffer 中的偏移量
    
    • offset 在序列化的过程中,当一个对象的成员属性个数超过一个时,JVM 会将会把所有的成员属性打包成一个“组”来操作,而 offset 就是这个组中当前描述的成员属性的偏移量,上层的 ObjectStreamClass 在调用当前这个成员属性的时候就使用偏移量进行引用定位操作;

    • signature 该属性描述了 JVM 中成员属性的类型签名

    JavaType TypeCode
    byte B
    short S
    int I
    long J
    float F
    double D
    char C
    boolean Z
    class L
    arrary [

    1.2 构造函数

    public ObjectStreamField(String name, Class<?> type, boolean unshared) {
        this.name = name;
        this.type = type;
        this.unshared = unshared;
        signature = getClassSignature(type).intern();
        field = null;
    }
    

    unshared 在 ObjectOutputStream源码分析的writeObject和writeUnshared区别进行了简单的说明,这里重点说一下 signature 这个属性,具体的方法如下:

    // JVM 中类型签名
    private static String getClassSignature(Class<?> cl) {
        StringBuilder sbuf = new StringBuilder();
        while (cl.isArray()) {
            sbuf.append('[');
            cl = cl.getComponentType();
        }
        if (cl.isPrimitive()) {
            if (cl == Integer.TYPE) {
                sbuf.append('I');
            } else if (cl == Byte.TYPE) {
                sbuf.append('B');
            } else if (cl == Long.TYPE) {
                sbuf.append('J');
            } else if (cl == Float.TYPE) {
                sbuf.append('F');
            } else if (cl == Double.TYPE) {
                sbuf.append('D');
            } else if (cl == Short.TYPE) {
                sbuf.append('S');
            } else if (cl == Character.TYPE) {
                sbuf.append('C');
            } else if (cl == Boolean.TYPE) {
                sbuf.append('Z');
            } else if (cl == Void.TYPE) {
                sbuf.append('V');
            } else {
                throw new InternalError();
            }
        } else {
            sbuf.append('L' + cl.getName().replace('.', '/') + ';');
        }
        return sbuf.toString();
    }
    

    2. ObjectStreamClass

    ObjectStreamClass 按官方的说法是类的序列化描述符,本质是对 Class 类的包装,提取了类序列化时的一些信息,包括字段的描述信息和 serialVersionUID 和需要序列化的字段 fields。本文只介绍一些 ObjectStreamClass 常用用法,更多关于ObjectStreamClass源码分析

    2.1 数据结构

    // 类的基本信息
    private Class<?> cl;            // 1. Class
    private String name;            // 2. cl.getName()
    private volatile Long suid;     // 3. serialVersionUID
    
    private boolean isProxy;        // 4. Proxy.isProxyClass(cl)
    private boolean isEnum;         // 5. Enum.class.isAssignableFrom(cl)
    private boolean serializable;   // 6. Serializable.class.isAssignableFrom(cl)
    private boolean externalizable; // 7. Externalizable.class.isAssignableFrom(cl)
    
    // Serializable 接口默认的方法,通过反射调用
    private Constructor<?> cons;            // 默认的构造函数
    private Method writeObjectMethod;       // writeObject
    private Method readObjectMethod;        // readObject
    private Method readObjectNoDataMethod;  // readObjectNoData
    private Method writeReplaceMethod;      // writeReplace
    private Method readResolveMethod;       // readResolve
    private boolean hasWriteObjectData;     // writeObjectMethod!=null
    
    // localDesc表示本类的描述信息,superDesc表示父类的描述信息
    private ObjectStreamClass localDesc;    // this
    private ObjectStreamClass superDesc;    // 父类,superDesc=lookup(superCl, false)
    
    // 要序列化的字段信息,通过 getSerialFields(c1) 获取
    private ObjectStreamField[] fields;     // 序列化的字段信息
    

    总结: 通过这些字段信息可以看到 ObjectStreamClass 提取了类序列化时的一些基本信息,这些信息大部分都是在其构造时就提取出来了。

    2.2 构造函数

    private ObjectStreamClass(final Class<?> cl) {
        // 1. 类的基本信息获取
        this.cl = cl;
        name = 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();
        superDesc = (superCl != null) ? lookup(superCl, false) : null;
        localDesc = this;
    
        // 2. Serializable 接口默认的方法,通过反射调用
        if (serializable) {       
            if (isEnum) {
                suid = Long.valueOf(0);
                fields = NO_FIELDS;
                return null;
            }
            if (cl.isArray()) {
                fields = NO_FIELDS;
                return null;
            }
    
            suid = getDeclaredSUID(cl);         // serialVersionUID
            try {
                fields = getSerialFields(cl);   // 序列化的字段信息
                computeFieldOffsets();
            } catch (InvalidClassException e) {
                serializeEx = deserializeEx = new ExceptionInfo(e.classname, e.getMessage());
                fields = NO_FIELDS;
            }
    
            if (externalizable) {
                cons = getExternalizableConstructor(cl);
            } else {
                cons = getSerializableConstructor(cl);
                writeObjectMethod = getPrivateMethod(cl, "writeObject",
                    new Class<?>[] { ObjectOutputStream.class }, Void.TYPE);
                readObjectMethod = getPrivateMethod(cl, "readObject",
                    new Class<?>[] { 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;
        }
    
        // 省略异常处理 ...
        initialized = true;
    }
    

    总结: 这个构造函数是私有的,可以通过 lookup 获取一个类的 ObjectStreamClass。

    // false 表示只获取实现了 Serializable 接口的类
    public static ObjectStreamClass lookup(Class<?> cl) {
        return lookup(cl, false);
    }
    // true 表示不管是否实现这个接口都提取相关的信息
    public static ObjectStreamClass lookupAny(Class<?> cl) {
        return lookup(cl, true);
    }
    

    2.3 提取序列号:getDeclaredSUID

    // 提取 serialVersionUID 字段信息
    private static Long getDeclaredSUID(Class<?> cl) {
        try {
            Field f = cl.getDeclaredField("serialVersionUID");
            int mask = Modifier.STATIC | Modifier.FINAL;
            if ((f.getModifiers() & mask) == mask) {
                f.setAccessible(true);
                return Long.valueOf(f.getLong(null));
            }
        } catch (Exception ex) {
        }
        return null;
    }
    
    public long getSerialVersionUID() {
        if (suid == null) {                 // 显示的配置了 serialVersionUID 就直接返回
            return computeDefaultSUID(cl);  // 生成一个默认的序列号 id
        }
        return suid.longValue();
    }
    

    总结: getDeclaredSUID 方法提取 serialVersionUID 字段信息。如果没有配置,getSerialVersionUID 方法会通过 computeDefaultSUID 生成一个默认的序列号。

    2.4 提取需要序列化字段:getSerialFields

    // 提取需要序列化字段
    private static ObjectStreamField[] getSerialFields(Class<?> cl)
        throws InvalidClassException {
        ObjectStreamField[] fields;
        if (Serializable.class.isAssignableFrom(cl) &&
            !Externalizable.class.isAssignableFrom(cl) &&
            !Proxy.isProxyClass(cl) &&
            !cl.isInterface()) {
            // serialPersistentFields 配置需要序列化的字段
            if ((fields = getDeclaredSerialFields(cl)) == null) {
                // 默认的序列化字段
                fields = getDefaultSerialFields(cl);
            }
            Arrays.sort(fields);
        } else {
            fields = NO_FIELDS;
        }
        return fields;
    }
    
    // 对外暴露的方法,获取可序列化的字段
    public ObjectStreamField[] getFields() {
        return getFields(true);
    }
        ObjectStreamField[] getFields(boolean copy) {
        return copy ? fields.clone() : fields;
    }
    

    总结: getDeclaredSerialFields 提取的是 serialPersistentFields 字段显示配置的 serialPersistentFields 需要序列化的字段,如果没有配置(大部分情况都是这样的)则提取默认的字段。

    // getDeclaredFields 提取本类中的所有字段,只要不是 static 或 transient 修辞的都会序列化
    private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
        Field[] clFields = cl.getDeclaredFields();
        ArrayList<ObjectStreamField> list = new ArrayList<>();
        int mask = Modifier.STATIC | Modifier.TRANSIENT;
    
        for (int i = 0; i < clFields.length; i++) {
            if ((clFields[i].getModifiers() & mask) == 0) {
                list.add(new ObjectStreamField(clFields[i], false, true));
            }
        }
        int size = list.size();
        return (size == 0) ? NO_FIELDS :
            list.toArray(new ObjectStreamField[size]);
    }
    

    2.5 其它方法

    private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
        // 获取要序列化的类,包括实现了 Serializable 接口的父类
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {  
            defaultWriteFields(obj, slots[i].desc);
        }
    }
    
    private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
        // 1. Java 原生类型序列化
        int primDataSize = desc.getPrimDataSize();      // 1.1 获取原生类型字段的长度
        if (primVals == null || primVals.length < primDataSize) {
            primVals = new byte[primDataSize];
        }
        desc.getPrimFieldValues(obj, primVals);         // 1.2 获取原生类型字段的值
        bout.write(primVals, 0, primDataSize, false);   // 1.3 原生类型序列化
    
        // 2. Java 对象类型序列化,递归调用 writeObject0 方法
        ObjectStreamField[] fields = desc.getFields(false);     // 2.1 获取所有序列化的字段
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);                   // 2.2 获取所有序列化字段的值
        for (int i = 0; i < objVals.length; i++) {              // 2.3 递归完成序列化
            writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());            
        }
    }
    

    总结: 其它用到的方法了解一下用法即可,就不往下深究了。

    参考:

    1. 《ObjectStreamClass源码分析》:https://blog.csdn.net/silentbalanceyh/article/details/8250096

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    Transaction And Lock--使用资源锁来控制并发
    曲演杂坛--页拆分
    维护建议--文件和文件组
    维护建议--开发设计
    维护建议--服务器磁盘
    维护建议--数据库备份
    TSQL--查找连续登陆用户
    shell脚本传递带有空格的参数的解决方法
    oozie无法识别hadoopHA中的ns1
    ERROR tool.ImportTool: Import failed: java.io.IOException: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf
  • 原文地址:https://www.cnblogs.com/binarylei/p/10989372.html
Copyright © 2011-2022 走看看