20.Java序列化【草案二】

(从09年回到重庆过后,就一直在工作,时间长了惰性就慢慢起来了,公司的项目从09年忙到了现在,一直没有时间来梳理自己的东西,CSDN的Blog似乎都荒废了,不知道现在还能否坚持把Blog完成,希望有一个新的开始吧!如果读者有问题还是可直接发邮件到[email protected],我也仅仅只是想把看的、写的、学的东西总结起来,让自己有个比较完整的学习记录。本文主要针对Java的序列化相关知识,先不涉及XML序列化和Json序列化的内容,这部分内容以后再议。着色的目的是强调重点和关键的概念以及防止读者通篇阅读的视觉疲劳,也是个人的写作风格,不习惯的读者请见谅!)

本章目录:

1.Java中的序列化

2.序列化原理和算法——基础数据

3.深入序列化规范

4.源码分析

----ObjectStreamField

----ObjectStreamClass

----ObjectOutputStream

----ObjectInputStream

5.序列化原理和算法——面向对象


4.源码分析

  【本章节的内容主要做的是源码分析,分析过程的概念性内容都是基于个人的理解,仅仅提供给读者参考,我尽可能在分析过程中保证这些概念性内容的准确性】

  前边的章节已经辅助读者解读了JVM的规范以及提供了比较详细的示例,这个章节主要完成下边三个目标:

  • 详细分析Java序列化中使用的几个核心类的源代码,通过其内部执行流程换一种角度来看看Java序列化的知识;
  • 针对JVM的序列化规范中的一些疑点提供示例来加以证明,进行规范的深入理解
  • 提供Java序列化中和面向对象、最佳实践的相关内容
  前几个章节的示例多次提到了Java序列化中的几个核心的类,这个章节主要看看下边2个类【蓝色部分】的源代码:
  • ObjectStreamField
  • ObjectStreamClass
  • ObjectOutputStream
  • ObjectInputStream
  i.ObjectStreamField源代码分析
  这个类主要用来提取序列化过程中某个对象内的字段【成员属性元数据信息,包括字段的类型、类型代码、签名等,为了让读者阅读该类源代码方便,我把代码拆开来解析,让读者更容易理解代码逻辑下边代码中:成员函数、函数、方法基本属于同一概念,请读者谅解
  1)类定义:
public class ObjectStreamField implements Comparable 
   
  
  该类实现了接口Comparable,这个接口主要用于“对象比较”,一旦实现了这个接口过后就必须实现接口中的方法compareTo,先看看在ObjectStreamField类中的compareTo的实现:
  compareTo:
    public int compareTo(Object obj) {
        ObjectStreamField other = (ObjectStreamField) obj;
        boolean isPrim = isPrimitive();
        if (isPrim != other.isPrimitive()) {
            return isPrim ? -1 : 1;
        }
        return name.compareTo(other.name);
    }
  相信读者还记得前边在解析规范和示例的时候提到了一个词语——字典序,这个词语就是实现Comparable接口以及上边这段代码存在的原因。这里再复习下Java对象中成员属性的二进制序列的生成规则:先根据字典序把基础类型的成员属性按属性名顺序排列,然后根据字典序把对象类型的成员属性按属性名顺序排列。从上边的函数可以知道:
  1. 两个成员属性在比较的时候分为两个维度进行比较:基础类型的数据只能和基础类型的数据进行比较,对象类型的数据只能和对象类型的数据进行比较;
  2. 两个成员属性在比较的时候,最终调用的是String类型的compareTo成员函数来比较成员属性的属性名
  3. 若返回-1则表示当前对象排在传入对象之前,为0则表示顺序不变,为1则表示当前对象应该排在传入对象之后
  isPrimitive:
  在比较两个字段的名称进行字典序排列的时候,compareTo方法调用了isPrimitive成员函数,该成员函数的定义如下:
    public boolean isPrimitive() {
        char tcode = signature.charAt(0);
        return ((tcode != 'L') && (tcode != '['));
    }
  ObjectStreamField类的实例描述了序列化的对象中成员属性元数据信息,上边的这个方法用于判断当前描述的成员属性一个基础类型的数据还是一个对象类型的数据,若当前描述的成员属性是基础类型这个函数返回true,反之返回false。该成员函数判断数据类型的方式是使用的签名中的类型代码来判断,前文多次提到类型代码的概念,目前可以知道对象类型的数据只有两种类型代码——数组array[对象objectL
  2)成员属性:
  ObjectStreamField类中定义了6成员属性
    /** field name */
    private final String name;
    /** canonical JVM signature of field type */
    private final String signature;
    /** field type (Object.class if unknown non-primitive type) */
    private final Class type;
    /** whether or not to (de)serialize field values as unshared */
    private final boolean unshared;
    /** corresponding reflective field object, if any */
    private final Field field;
    /** offset of field value in enclosing field group */
    private int offset = 0;
  这6成员属性的含义如下:
  • name——java.lang.String
    该属性描述了序列化的成员属性的名称
  • signature——java.lang.String
    该属性描述了JVM中成员属性的类型签名,注:成员属性的类型、类型代码、类型签名不是同一个概念,这三个概念在下文会加以区分;
  • type——java.lang.Class
    该属性描述了成员属性的类型
  • unshared——boolean
    该属性标识了当前成员属性是否具有“unshared”的语义,“unshared”的对象在规范的解析中提到过,这里不重复;
  • field——java.lang.reflect.Field
    该属性描述了当前成员属性的JVM级别的元数据信息,在序列化提取成员属性的元数据信息时,会使用Java语言中的“反射”,该成员的类型是java.lang.reflect.Field
  • offset——int
    在序列化的过程中,当一个对象的成员属性个数超过一个时,JVM会将会把所有的成员属性打包成一个“组”来操作,而offset就是这个组中当前描述的成员属性的偏移量,上层的ObjectStreamClass在调用当前这个成员属性的时候就使用偏移量进行引用定位操作;
  注意上边成员属性的定义,除了offset偏移量以外,其他几个成员属性的定义中都加入了final关键字,也就是说name、signature、type、unshared、field五个成员属性只能在构造函数中执行初始化,而且一旦初始化过后:基础数据不能更改对象数据的引用不可再指向其他对象。从设计上讲该类中这五个属性不会有set*前缀Java Bean规范标准方法,因为成员是final的,即使提供了set*前缀的方法也无济于事,而六个成员属性中offset还拥有默认值0,也就是说在元数据中若使用偏移量遍历一个类的成员属性的时候,其偏移量应该和数组的索引规范保持一致,起始值都从0开始,而不是1,如果索引非法则返回-1Unsafe.INVALID_FIELD_OFFSET
  区分成员属性类型、类型代码、类型签名:这三个概念这里希望读者通过其格式加以区分【以String类型为例】。
  类型——Java的成员属性的类型一般对应的Java数据类型Class,它也可以使用格式“String.class”表示,如果是引用可以使用Class<String>进行定义,这是直接基于JVM级别的类型,一般这种数据会提供给JVM用来执行“反射”等操作;
		Class cs = String.class;
  类型代码——类型代码的数据也是用于JVM判断成员属性数据类型的一种方式,但类型代码的Java数据类型是char,比如‘L’,它一般通过一个字符来判断当前的Java数据类型,序列化时它会把这个字符转换成二进制数据
  类型签名——后边有例子讲解JVM如何处理类型签名,类型签名本身的Java数据类型是一个String类型,比如:‘Ljava/lang/String;’,它和类型代码一样可以用于JVM判断成员属性的数据类型,但是不仅仅如此,JVM在处理类型签名的时候,针对成员属性、成员函数、类本身都可以使用统一的方式来区分,在JVM里面类型签名相当于类型的唯一标识,它的使用范围比类型代码更加广阔;
  3)Java Bean规范方法:
  针对上边的五个成员属性,该类提供了五个成员属性的Get/Set方法,设置/获取函数
  Get方法定义清单:
public String getName() {
    return name;
}
String getSignature() {
    return signature;
}
public Class getType() {
    return type;
}
public boolean isUnshared() {
    return unshared;
}
Field getField() {
    return field;
}
public int getOffset() {
    return offset;
}
  Set方法定义清单:
protected void setOffset(int offset) {
    this.offset = offset;
}
  注意这几个方法的访问控制修饰符setOffset方法的修饰符是protected,意味着这个方法只能在本类以及子类可以调用;而getFieldgetSignature两个方法的修饰符都是默认default域,只能在java.io的包中可以调用这两个方法。可以这样去理解,通过java.io包之外的类针对成员属性进行元数据提取的时候,只能通过几个基本方法获取到这个成员属性的名称、类型、偏移量禁用“反射”【getField不可用,也就是说Java语言中虽然序列化是可扩展的,但是严格限制了其扩展的方式。
  4)构造函数:
  接下来仔细看看ObjectStreamField的四个构造函数的执行逻辑:
  两个public构造函数内容如下:
public ObjectStreamField(String name, Class type) {
    this(name, type, false);
}
public ObjectStreamField(String name, Class type, boolean unshared) {
    if (name == null) {
        throw new NullPointerException();
    }
    this.name = name;
    this.type = type;
    this.unshared = unshared;
    signature = getClassSignature(type).intern();
    field = null;
}
  • 第一个构造函数调用了第二个构造函数的内容,其中第三个参数传入的是false,也就是说默认情况下通过两参构造ObjectStreamField的时候,构造的是一个"unshared"的成员属性,从序列化的原理上来讲前一章规范中已经注明,不论是类还是字段,"unshared"的状态下JVM都会为其创建新的元数据信息,而默认情况下是共享元数据信息的,在生成二进制序列的时候并不为每一个成员属性或者类单独创建元数据信息;
  • 使用public构造函数的时候,基本成员属性【字段】中,field属性的初始化值为null;而name、type、unshared都是传入的;比较特殊的属性是signature,它的初始值是使用的getClassSignature方法来获取的;
  注意这两个构造函数,这两个构造函数提供了一个快速创建成员属性【字段】元数据的构造方式,但它并没为field赋值,也就是说除了字段的名称、类型、“unshared”的状态以及签名以外,这两个构造函数没有实质性的赋值——可以理解成这两个构造函数都处于“半构造”的状态,它还需要进一步构造才能获取针对字段的完整元数据【ObjectStreamField,当然有可能field本身对元数据的提取没有影响,这里这种构造方法的定义可能是出于设计上的考虑。
  两个default默认域构造函数如下:
    ObjectStreamField(String name, String signature, boolean unshared) {
        if (name == null) {
            throw new NullPointerException();
        }
        this.name = name;
        this.signature = signature.intern();
        this.unshared = unshared;
        field = null;

        switch (signature.charAt(0)) {
            case 'Z': type = Boolean.TYPE; break;
            case 'B': type = Byte.TYPE; break;
            case 'C': type = Character.TYPE; break;
            case 'S': type = Short.TYPE; break;
            case 'I': type = Integer.TYPE; break;
            case 'J': type = Long.TYPE; break;
            case 'F': type = Float.TYPE; break;
            case 'D': type = Double.TYPE; break;
            case 'L':
            case '[': type = Object.class; break;
            default: throw new IllegalArgumentException("illegal signature");
        }
    }
    ObjectStreamField(Field field, boolean unshared, boolean showType) {
        this.field = field;
        this.unshared = unshared;
        name = field.getName();
        Class ftype = field.getType();
        type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
        signature = getClassSignature(ftype).intern();
    }
  • 两个default构造函数不存在相互调用关系,和public两个构造函数的相互调用关系不一样;
  • 第一个default构造函数中,name、signature、unshared都是直接传入的,field直接设置为null,没有经过复杂的运算;而type使用了Java序列化中的类型代码Type Code映射转换,在第三章的序列化规范中可以看到每一个成员属性都有固定的Type Code,而在构造一个ObjectStreamField的时候,其type属性的运算是根据Type Code的值来换算的;若获取的Type Code不对则直接抛出IllegalArgumentException的异常;
  • 第二个default构造函数中,field、unshared两个成员属性是直接传入的,name属性从field中提取,而signature是通过当前传入的field的类型作为getClassSignature方法的参数计算获取;showType在这个构造函数中主要是为了序列化实现的兼容性而考虑——如果当前成员属性是一个基础类型的数据isPrimitive方法返回true,它将直接返回基础数据的类型;若showType传入的值也是true,那么即使不检查成员属性的类型也会返回,这种情况用于对象类型的获取。推测type运算的正常逻辑如下:
    -- 如果当前字段是一个基础类型,则返回基础类型getType的值;
    -- 如果当前字段是一个自定义对象类型,则返回对象类型getType的值;
    -- 以上两者都不是的情况,返回Object.class
  分析上述的四个构造函数,只有最后一个函数为field成员属性赋值了,也就是说只有最后一个函数是提供了完整构造过程构造函数,但是因为前三个构造函数有type成员属性在,所以这种非完整的构造块在设计上应该有它自己的理由。这一点的话希望读者在实际开发过程多思考去理解,其实ObjectStreamField常用的外部API会和serialPersistentFields的重写有关,重写它的时候会构造一个ObjectStreamField[]的类型。
  5)String类的intern方法:
  在上边的构造函数中,多次调用了String类的intern方法,这个方法的主要用法为:在构造一个新的字符串的时候,会先在常量池中检测该String对象是否存在【存在与否调用String对象的equals函数】,如果已经存在这样的String对象则直接从常量池中返回该字符串,否则的话新的字符串对象会被加入到常量池,最终会返回新加入的String对象的引用。
  从上边的解读可以知道,在序列化解析成员属性的元数据的时候,其成员属性的类型签名通过String类的intern方法保证了唯一性,比如有两个String类型的对象obj1和obj2,它们的类型签名是一致的,而在构造ObjectStreamField的过程中,这个签名也具有唯一性——简单讲:同样类型的成员属性共享类型签名
  6)getClassSignature(Class)
  ObjectStreamField中拥有一个私有静态方法,用于计算当前成员属性的类型签名,其定义如下:
    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();
    }
  这个函数的返回值会是什么内容呢【涉及类型签名的格式】?先看一个例子:
package org.susan.java.serial;

import java.util.ArrayList;
import java.util.List;

public class FieldSignature {
	public 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();
    }
	public static void main(String args[]){
		// void类型
		System.out.println(FieldSignature.getClassSignature(void.class));
		// 基础类型
		System.out.println(FieldSignature.getClassSignature(int.class));
		// 基础封装类型
		System.out.println(FieldSignature.getClassSignature(Integer.class));
		// 自定义对象类型
		System.out.println(FieldSignature.getClassSignature(FieldSignature.class));
		// String类型
		System.out.println(FieldSignature.getClassSignature(String.class));
		// 数组类型
		int[] arr = new int[2];
		System.out.println(FieldSignature.getClassSignature(arr.getClass()));
		// 对象数组
		FieldSignature[] signs = new FieldSignature[4];
		System.out.println(FieldSignature.getClassSignature(signs.getClass()));
		// 对象集合
		List list = new ArrayList();	
		System.out.println(FieldSignature.getClassSignature(list.getClass()));
	}
}
  上边这段代码的输出如下:
V
I
Ljava/lang/Integer;
Lorg/susan/java/serial/FieldSignature;
Ljava/lang/String;
[I
[Lorg/susan/java/serial/FieldSignature;
Ljava/util/ArrayList;

  从这样的一段代码基本可以推测成员属性的类型签名的格式:
  • Java中有一个特殊的Class类型——Void.class,这个Class的构造函数是私有的,所以它不具有实例,除了上边的void.classVoid.class等价】可以直接使用,而成员属性不具有这个类型的;但是这里的解析仅仅只涉及了成员属性的签名,没有涉及成员函数的签名,成员函数的签名中函数的返回值是可以返回void类型的,也就是它的返回值类型可以是Void.class
  • 所有的基础数据类型,其类型签名就是一个固定的字符串,类似:I、B、J、F、D、S、C、Z等,如果基础类型的成员属性不属于上述八种类型中的任何一种,同样也不是V表示的void类型,则抛出InternalError的错误;
  • 所有的数组使用的签名第一个字符[符号,紧随其后的是数组中元素的类型签名, 对比上边的arr数组和signs数组的签名,它们一个是int[]类型,另外一个是FieldSignature[]的类型,看看输出就知道其元素签名的区别;从区别中可以知道的是所有对象数组的签名也具有唯一性,它的类型签名的前缀为[L
  • 所有的对象签名都是以L开始,后边跟上一个固定格式来描述该对象的类型,这个固定格式一般是对象的类全名替换'.'‘/’,然后加上';'的后缀;
  • 最后有一点容易忽略:只要是基础类型【包括非基础类型的数组】,其签名的末尾都会有分号作为结束标识
  7)getTypeCode、getTypeString以及isUnshared
  到这里ObjectStreamField就仅仅只剩下三个函数了,这三个函数中最后一个isUnshared函数这里不解析,看看另外两个函数:
  getTypeCode:
    public char getTypeCode() {
        return signature.charAt(0);
    }
  在前边分析二进制序列和解读序列化规范的时候,多次提到了Type Code这个概念,那么到这里Type Code面纱也被揭开了,实际上Type Code的取值内容就是根据成员属性的类型签名进行运算的:它的值是类型签名第一个字符
  getTypeString:
    public String getTypeString() {
        return isPrimitive() ? null : signature;
    }
  从getTypeString的函数可以知道:在序列化过程中,基础数据类型【不包含封装类型,封装类型本身是Object的子类是不会包含类型的描述的,看看JVM中字节流的语法:
primitiveDesc:
  prim_typecode fieldName
objectDesc:
  obj_typecode fieldName className1
  从上边的字节流语法可以知道,getTypeString这个方法是为className1这段语法量身订造的方法,这里的className1实际值就是成员属性类型签名

  ii.ObjectStreamClass源码分析
  前一个章节分析了ObjectStreamField的源代码,这个章节将要分析一个更加复杂的类:ObjectStreamClass,这个类主要用来提取序列化过程中某个对象所属类元数据信息,对象所属类包含的元数据信息比起它的成员属性包含的元数据信息要复杂许多。序列化在解析类以及类中定义的成员属性的时候使用了下边这种结构:
ObjectStreamClass --ObjectStreamField[]
  针对Class的元数据而言使用ObjectStreamClass作为元数据的存储容器,针对Class中的成员属性对应的元数据信息而言使用ObjectStreamField[]作为元数据的存储容器,而字段元数据的数组中的每一个元素类型对应的都是ObjectStreamField类型,它提取了单个成员属性的元数据信息。
  1)类定义:
public class ObjectStreamClass implements Serializable
  该类实现了接口Serializable,也就是说这个类本身就包含了“可序列化”的语义,可以这样理解,Java在序列化一个对象的时候,会先提取出这个对象的元数据信息,通过这个对象生成对应的ObjectStreamClass对象,而ObjectStreamClass对象主要描述了当前序列化对象所属类的元数据信息。
  2)内部类:
  ObjectStreamClass中定义了许多内部类,其内部类的所有定义如下:
private static class Caches
private static class ExceptionInfo
private static class EntryFuture
static class ClassDataSlot
private static class MemberSignature
private static class FieldReflector
private static class FieldReflectorKey extends WeakReference>
static class WeakClassKey extends WeakReference>
  在分析ObjectStreamClass本身的源代码之前先看看每一个内部类的作用:
  Caches类【缓存类】:
  这个类中只定义了四个固定的属性,该类的完整定义如下:
    private static class Caches {
        /** cache mapping local classes -> descriptors */
        static final ConcurrentMap> localDescs =
            new ConcurrentHashMap<>();
        /** cache mapping field group/local desc pairs -> field reflectors */
        static final ConcurrentMap> reflectors =
            new ConcurrentHashMap<>();
        /** queue for WeakReferences to local classes */
        private static final ReferenceQueue> localDescsQueue =
            new ReferenceQueue<>();
        /** queue for WeakReferences to field reflectors keys */
        private static final ReferenceQueue> reflectorsQueue =
            new ReferenceQueue<>();
    }
  【*:这里的泛型语法new T<>()是JDK 1.7中的语法】这个类属于缓存类,它定义了四个成员:
  localDescsQueuereflectorsQueue两个成员是私有的静态成员,数据结构类型为“队列”;因为这两个成员属性加了final修饰,也就是说这两个引用的对象一旦初始化过后,引用不能指向新对象,它们具有JVM级别的唯一性;因为类中加入了private修饰,所以这两个成员不能外部使用,只能在ObjectStreamClass内使用;又因为这两个成员使用了static修饰,属于静态成员,它们不属于对象而属于类;这两个成员的使用涉及了Java中的弱引用【Weak Reference,它所定义的两个队列为两个弱引用的队列;localDescsQueue中存储的成员是本地类的类描述符成员,但是它只保存了类描述符成员对应的Key值【WeakClassKey类型reflectorsQueue中的成员虽然也是Class的类型,但使用时应该用做FieldReflectorKey类型的队列容器,它可以用于引用FieldReflector对象,针对每一个类的分析而言它拥有一个FieldReflector来分析类中字段的元数据
  localDescsreflectors的两个成员是默认default域的静态成员,类型为“哈希表映射”,同样也加入了final修饰,也就是说这两个成员同样具有JVM级别的唯一性,不仅仅如此,这两个成员充当了缓存的作用;localDescs成员的哈希表键为一个WeakClassKey类型,它的值为一个抽象类型Reference,这个抽象类为SoftReference【软引用WeakReference【弱引用的父类,它指向了WeakClassKey对应的类描述符对象reflectors成员的哈希表键为FieldReflectorKey类型,它的值同样为一个抽象类型Reference,它指向了FieldReflectorKey对应的字段描述信息
  结合上边的定义,这四个成员可以用下边的图进行结构解析,希望读者理解Caches中四个成员的关系:

  【*:Caches类定义了两部分内容:第一部分是和类描述信息有关的,第二部分是和字段信息有关的,这两部分内容各自包含了一个队列容器和哈希表,它们都具有唯一性。至于为什么使用软引用、弱引用在后边分析的时候希望可以给出答案。使用ConcurrentMap的目的是为了保证序列化时的性能、可伸缩性以及并发操作】。
  简单提及一下ConcurrentMap的知识点:
  ConcurrentMap的主要子类是ConcurrentHashMap,该对象中包含了多个segment,而每一个segment中都包含了一个Entity的数组,而它的使用过程中比普通的HashMap多了一个segment类,segment类继承于ReentrantLock类,也就是说它实际上是一个锁。当多个线程操作ConcurrentHashMap时,JVM不会完全锁住这个map,而是锁住一个segment,这种情况下就提高了并发的效率。它的优缺点:
  优点——由于是对对应的segment加锁,不是锁定整个map,并发性得到提高,能够直接提高插入、检索以及移除操作的可伸缩性;
  缺点——当遍历map中的元素时,需要获取所有segment的锁,使用遍历会很慢。锁的增多占用了系统的资源,当调用针对整个集合的方法,比如isEmptysize的实现更加困难,而且结果可能会有错误的风险。
  ExceptionInfo类【异常类】:
  该类中只包含了一个异常类的基本类名和异常信息,它的完整定义如下:
    private static class ExceptionInfo {
        private final String className;
        private final String message;

        ExceptionInfo(String cn, String msg) {
            className = cn;
            message = msg;
        }

        /**
         * Returns (does not throw) an InvalidClassException instance created
         * from the information in this object, suitable for being thrown by
         * the caller.
         */
        InvalidClassException newInvalidClassException() {
            return new InvalidClassException(className, message);
        }
    }
  这个类中主要包含了两个成员属性:classNamemessage,这个类主要用于检测InvalidClassException异常,classNamemessage两个成员属性都包含了final修饰符,这个类的所有实例都是不可变的,而且它们会共享一个ObjectStreamClass对象,而该类中会根据classNamemessage两个成员属性生成一个新的InvalidClassException对象。在ObjectStreamClass检测被解析的类时,如果出错会抛出该异常信息,ExceptionInfo和一般的异常定义不一样,它并没有从Exception类中继承来定义异常,而是直接通过调用newInvalidClassException方法生成一个新的异常,而且这个异常信息会根据成员属性classNamemessage中存储的数据来生成。
  EntryFuture类
  该类用于在已经初始化的进程中从一个进程入口去检索类描述符以及字段反射器的相关信息,当一个内部调用者从一个进程内的另外线程去获取一个EntryFuture实例的时候,它应该调用EntryFutureget方法,这个方法将会返回这个实例本身,EntryFuture实例本身使用了线程同步的方法定义。先看看这个类的整体定义:
    private static class EntryFuture {

        private static final Object unset = new Object();
        private final Thread owner = Thread.currentThread();
        private Object entry = unset;

        /**
         * Attempts to set the value contained by this EntryFuture.  If the
         * EntryFuture's value has not been set already, then the value is
         * saved, any callers blocked in the get() method are notified, and
         * true is returned.  If the value has already been set, then no saving
         * or notification occurs, and false is returned.
         */
        synchronized boolean set(Object entry) {
            if (this.entry != unset) {
                return false;
            }
            this.entry = entry;
            notifyAll();
            return true;
        }

        /**
         * Returns the value contained by this EntryFuture, blocking if
         * necessary until a value is set.
         */
        synchronized Object get() {
            boolean interrupted = false;
            while (entry == unset) {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    interrupted = true;
                }
            }
            if (interrupted) {
                AccessController.doPrivileged(
                    new PrivilegedAction() {
                        public Void run() {
                            Thread.currentThread().interrupt();
                            return null;
                        }
                    }
                );
            }
            return entry;
        }

        /**
         * Returns the thread that created this EntryFuture.
         */
        Thread getOwner() {
            return owner;
        }
    }
  注意这个类:这个类中的EntryFuture实例是隶属于一个Java线程的,而每一个EntryFuture实例都拥有一个Owner线程,从这个类的set方法可以知道,EntryFuture中的entry只能被设置一次,一旦设置过后再调用set方法将会返回false。实际上EntryFuture是针对ObjectStreamClass中被解析类的实例的一个封装,主要目的是为了解决多线程环境的序列化问题而存在的。在代码中,如果需要创建一个被解析类的实例,会先创建一个EntryFuture的实例,然后通过EntryFuture实例去获取被解析类的实例,通过这种方式的操作可以保证在多线程环境中被解析类实例的线程安全性——从设计上来讲这个类的存在是必须的。不仅仅如此,从代码细节可以发现,在获取一个被解析类的实例时【调用get方法】,系统调用了Java中的访问控制器检测了代码的执行权限。
  【*:在分析ObjectStreamClass源代码之前,先解析里面的Class定义,其目的是为了知道ObjectStreamClass中拥有多少内部类,从这些内部类的定义可以知道它们大部分的修饰符都是private的,所以只能在ObjectStreamClass中使用,了解了它们过后分析ObjectStreamClass就相对简单许多。】
  ClassDataSlot类:
  这个类主要用来描述已经分配的类描述符数据中可序列化对象的部分类描述信息,如果hasData的值为false,则表示对象的可序列化形式【JVM规范中已经说明,前一个章节的第4部分:类描述符中有说明】不包含和类描述符相关到数据信息。
    static class ClassDataSlot {

        /** class descriptor "occupying" this slot */
        final ObjectStreamClass desc;
        /** true if serialized form includes data for this slot's descriptor */
        final boolean hasData;

        ClassDataSlot(ObjectStreamClass desc, boolean hasData) {
            this.desc = desc;
            this.hasData = hasData;
        }
    }
  从该类的定义可以看到它包含了两个成员,成员hasData前边已经说过,另外一个成员desc描述了这个类和主类ObjectStreamClass从属关系。思考一下,为什么会需要这种从属关系?请读者随时注意ObjectStreamClass的运行环境,它的运行环境包含两部分,所有静态成员是在“类环境”中运行的,而实例的运行环境则是“对象环境”ClassDataSlot实际上是对ObjectStreamClass的一种封装,它的这种封装可以保证在处理类的元数据过程中更加有序,相当于在读写类的元数据时多抽象了一层来执行庞大的管理操作。
  MemberSignature类【成员签名类】:
  这个类主要提供给ObjectStreamClass在计算serialVersionUID字段时,用来运算以及缓存成员属性、构造函数、成员方法的签名信息。
    private static class MemberSignature {

        public final Member member;
        public final String name;
        public final String signature;

        public MemberSignature(Field field) {
            member = field;
            name = field.getName();
            signature = getClassSignature(field.getType());
        }

        public MemberSignature(Constructor cons) {
            member = cons;
            name = cons.getName();
            signature = getMethodSignature(
                cons.getParameterTypes(), Void.TYPE);
        }

        public MemberSignature(Method meth) {
            member = meth;
            name = meth.getName();
            signature = getMethodSignature(
                meth.getParameterTypes(), meth.getReturnType());
        }
    }
  该类有三个成员属性member、name、signature,member会使用Java的“反射”来分析成员的JVM级别的元数据信息,name为成员名称,signature为成员的签名信息,它的构造函数有三个【分别处理成员属性、构造函数、成员函数三种情况】
public MemberSignature(Field field)
public MemberSignature(Constructor cons)
public MemberSignature(Method meth)
  Field单参数构造函数调用了私有成员函数getClassSignature,这个函数和ObjectStreamField中的成员函数是一样的,这里不再重复;
  ConstructorMethod单参数构造函数都调用了私有成员函数getMethodSignature,先看看这个函数的定义:

    private static String getMethodSignature(Class[] paramTypes,
                                             Class retType)
    {
        StringBuilder sbuf = new StringBuilder();
        sbuf.append('(');
        for (int i = 0; i < paramTypes.length; i++) {
            sbuf.append(getClassSignature(paramTypes[i]));
        }
        sbuf.append(')');
        sbuf.append(getClassSignature(retType));
        return sbuf.toString();
    }
  同样通过一个例子来看看getMethodSignature函数的输出内容:
package org.susan.java.serial;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class MethodSignature {
	public static String getMethodSignature(Class[] paramTypes,
			Class retType) {
		StringBuilder sbuf = new StringBuilder();
		sbuf.append('(');
		for (int i = 0; i < paramTypes.length; i++) {
			sbuf.append(FieldSignature.getClassSignature(paramTypes[i]));
		}
		sbuf.append(')');
		sbuf.append(FieldSignature.getClassSignature(retType));
		return sbuf.toString();
	}
	public static void main(String args[]){
		// 构造函数签名
		Constructor[] cons = MethodTest.class.getDeclaredConstructors();
		for( Constructor con: cons){
			System.out.println("==>" + con.getName());
			System.out.println(getMethodSignature(con.getParameterTypes(),Void.TYPE));
		}
		// 方法签名
		Method[] methods = MethodTest.class.getDeclaredMethods();
		for( Method method: methods){
			System.out.println("==>" + method.getName());
			System.out.println(getMethodSignature(method.getParameterTypes(),method.getReturnType()));
		}
	}
}

class MethodTest{
	private int age;
	private String name;
	public MethodTest(int age, String name){
		this.age = age;
		this.name = name;
	}
	public MethodTest(int age){
		this(age,"Unknown");
	}
	public String buildTemplate(String prefix){
		return prefix + " " + this.name + ", you are " + this.age + " now.";
	}
	public void buildHello(){
		String template = buildTemplate("Hello");
		System.out.println(template);
	}
}
  上边的示例输出信息如下:
==>org.susan.java.serial.MethodTest
(ILjava/lang/String;)V
==>org.susan.java.serial.MethodTest
(I)V
==>buildHello
()V
==>buildTemplate
(Ljava/lang/String;)Ljava/lang/String;

  请读者仔细去理解方法签名的格式,在理解方法签名的时候需要结合前面提到的getClassSignature方法来理解,因为不论是构造函数还是成员函数参数、返回值都调用了getClassSignature方法来生成对应的类型签名,到这里相信不用解析读者就知道为什么在getClassSignature签名的时候会有一个针对void类型的判断,虽然成员属性的签名中不会包含void类型,但成员函数的返回值可能会出现void类型,所以有了类型代码'V'出现。MemberSignature类主要目的是针对类中成员属性进行了封装,使用统一的格式进行管理,它在处理成员时使用了Java反射中的Member接口,这个接口比起可实例化的Field、Method、Constructor而言更加抽象,所以该类在成员属性这个级别抽象了一级出来用来解析三种不同类型的成员属性,则它的这种封装其数据本身描述了“成员属性的元数据”
  FieldRelfectorKey、WeakClassKey
  因为FieldReflector类相对比较复杂,在解析这个类之前先看看FieldRelfectorKeyWeakClassKey,早在上边的Caches类中已经提到两个哈希表的键值一个类型是WeakClassKey【localDescs,一个类型是FieldReflectorKey【reflectors,先看看两个类各自的源码定义:
  FieldRelfectorKey的定义如下:
    private static class FieldReflectorKey extends WeakReference> {

        private final String sigs;
        private final int hash;
        private final boolean nullClass;

        FieldReflectorKey(Class cl, ObjectStreamField[] fields,
                          ReferenceQueue> queue)
        {
            super(cl, queue);
            nullClass = (cl == null);
            StringBuilder sbuf = new StringBuilder();
            for (int i = 0; i < fields.length; i++) {
                ObjectStreamField f = fields[i];
                sbuf.append(f.getName()).append(f.getSignature());
            }
            sigs = sbuf.toString();
            hash = System.identityHashCode(cl) + sigs.hashCode();
        }

        public int hashCode() {
            return hash;
        }

        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (obj instanceof FieldReflectorKey) {
                FieldReflectorKey other = (FieldReflectorKey) obj;
                Class referent;
                return (nullClass ? other.nullClass
                                  : ((referent = get()) != null) &&
                                    (referent == other.get())) &&
                    sigs.equals(other.sigs);
            } else {
                return false;
            }
        }
    }
  这个类主要在缓存表中提供了一个哈希表键,如果两个引用指向了同一个类,则它们为相同的键,也就是说缓存中的同一个特本地类描述符在系统中应该只出现一次。关于FieldReflectorKey的详细定义这里就不解析重写的hashCode和equals方法了,主要看看这个类的定义和构造函数。该类拥有一个父类WeakReference>,也就是说这个类的实例引用是Java中的弱引用,看看它的构造函数中的主要逻辑:
  1. 调用父类WeakReference的构造函数;
  2. 如果传入的第一个参数clnull,则将成员函数nullClass设置成true,该成员函数是一个标识,判断传入的类是否为null
  3. 分析第二个参数fields生成传入Class的整体字段签名
  4. 将整体字段签名赋值给成员属性sigs
  5. 计算签名过后的hash值,根据传入的Class的hashCode以及签名sigshashCode计算;
  这个类中的sigshash值很重要,实际上在重写的equalshashCode方法中,判断两个FieldReflectorKey实例指向的Class是否相同使用的判断依据就是sigs属性以及hash值,这两个值在FieldReflectorKey中是固定算法的,而且它计算源是根据构造函数中的传入Class来计算的,所以仅仅针对同一个Class而言这两个值是相等的
  WeakClassKey的定义如下:
    static class WeakClassKey extends WeakReference> {
        /**
         * saved value of the referent's identity hash code, to maintain
         * a consistent hash code after the referent has been cleared
         */
        private final int hash;

        /**
         * Create a new WeakClassKey to the given object, registered
         * with a queue.
         */
        WeakClassKey(Class cl, ReferenceQueue> refQueue) {
            super(cl, refQueue);
            hash = System.identityHashCode(cl);
        }

        /**
         * Returns the identity hash code of the original referent.
         */
        public int hashCode() {
            return hash;
        }

        /**
         * Returns true if the given object is this identical
         * WeakClassKey instance, or, if this object's referent has not
         * been cleared, if the given object is another WeakClassKey
         * instance with the identical non-null referent as this one.
         */
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (obj instanceof WeakClassKey) {
                Object referent = get();
                return (referent != null) &&
                       (referent == ((WeakClassKey) obj).get());
            } else {
                return false;
            }
        }
    }
  这个类作为缓存中哈希表的键值而言只是比FieldReflectorKey少了sigs以及nullClass属性,这里就不重复分析了,细心的读者理解了FieldReflectorKey过后自然就明白这个类了。
  FieldReflector
  最后一起来看看FieldReflector类,这个类是ObjectStreamClass中最复杂的一个内部类,这个类主要用于针对序列化中对象的可序列化字段进行分析,它为每一个被解析的类提供了一个FieldReflector分析该类中所有字段信息,它操作的核心数据结构为ObjectStreamField[]类型。因为它的结构复杂所以在本文分析的时候同样使用拆分的方式拆开来看这个类的结构。
  FieldReflector成员属性定义如下:
        /** handle for performing unsafe operations */
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        /** fields to operate on */
        private final ObjectStreamField[] fields;
        /** number of primitive fields */
        private final int numPrimFields;
        /** unsafe field keys for reading fields - may contain dupes */
        private final long[] readKeys;
        /** unsafe fields keys for writing fields - no dupes */
        private final long[] writeKeys;
        /** field data offsets */
        private final int[] offsets;
        /** field type codes */
        private final char[] typeCodes;
        /** field types */
        private final Class[] types;
  先看看这个内部类中的所有成员:
  • unsafe——sun.misc.Unsafe
    该成员主要用于处理一些不安全的操作,这个操作偏向于CAS硬件原语级别【后边会讲到】,而不是单纯的Java语言级的内容;
  • fields——java.io.ObjectStreamField[]
    fields成员用于描述当前的ObjectStreamClass中的成员属性描述集合,前边已经讲过ObjectStreamField类可以分析成员属性得到它对应的元数据信息,而fields成员属性保存了当前分析的类对应的所有成员属性元数据信息,它是一个数组结构,每一个元素都对应了成员属性的元数据描述信息,且不会重复。
  • numPrimFIelds——int
    该属性描述了当前ObjectStreamClass中基础类型的成员属性的数量
  • readKeys——long[]
    针对不安全的字段的读取提供的键值,这个键值可能包含了重复值【CAS原语级别】
  • writeKeys——long[]
    针对不安全的字段的写入提供的键值,这个键值不包含重复值【CAS原语级别】
  • offsets——int[]
    前边在分析ObjectStreamField的时候讲过,在序列化分析一个类中定义的成员属性的时候,依靠偏移量来读取每一个成员属性,而FieldReflector是针对当前Class的所有成员属性进行分析的类,所以它会有一个偏移量的集合,offsets就是保存偏移量的数组
  • typeCodes——char[]
    typeCodes数组包含了一个Class中所有成员属性的类型代码的集合;
  • types——java.lang.Class[]
    types数组包含了一个Class中所有成员属性的类型的集合;
  getFields方法
  在FieldReflector类中包含了一个getFields方法,用于获取所有的字段元数据信息ObjectStreamField[]类型】,它的定义如下:
        ObjectStreamField[] getFields() {
            return fields;
        }
  这个方法会获得当前ObjectStreamClass类中所有分析的字段元数据信息,比如shared/unshared、字段类型、字段值等。
  set/get特殊方法
  除了上边的方法以外,这个类中还定义了四个特殊方法,这四个方法的定义如下不提供实现源代码
        void getPrimFieldValues(Object obj, byte[] buf) {...}
        void setPrimFieldValues(Object obj, byte[] buf) {...}
        void getObjFieldValues(Object obj, Object[] vals) {...}
        void setObjFieldValues(Object obj, Object[] vals) {...}  
  阅读的时候注意上边的get*类型的方法,所有get*方法的返回值都是void的,这和常用的get*方法不太一样。
  getPrimFieldValues方法主要用于基础类型字段的序列化,它在执行之前会确保FieldReflector获取的类描述符已经通过了checkDefaultSerialize()序列化的检测,如果当前数组中不包含任何可读取的键值,则其值应该和Unsafe.INVALID_FIELD_OFFSET相等。这个方法会使用readKeys、offsets、typeCodes成员,主要用于将序列化过后的基础类型字段写入到buf字节缓冲区中;
  setPrimFieldValues方法是对应get方法的逆方法,它主要用于基础类型字段反序列化操作,它会先从writeKeys中去获取字段对应的键值,如果出现了Unsafe.INVALID_FIELD_OFFSET的键值直接放弃该字段的设置行为。这个方法会使用writeKeys、offsets、typeCodes成员,它主要用于将读取的字节数据写入到当前传入的对象中,设置该对象对应的成员属性的值;
  getObjFieldValues方法和setObjFieldValues方法在这里就不重复了,它们和上边提到的两个方法的用法是一致的,唯一的不同是它们的操作对象是Java中的对象类型的字段而不是基础类型的字段;
  注意这里的writeKeysreadKeys方法,从前文中读者可以知道一般写操作是:把Java对象的状态转换成字节流,而读操作是:把字节流中的数据恢复成Java对象。但从源代码中可以知道这里的读写概念相反,writeKeys负责将字节流中的数据写入到Java对象,而readKeys负责将Java对象中的字段值读取出来反向写入到字节流中;为什么会这样呢?因为操作对象不一样,前文提到的序列化反序列化的操作对象是目标介质比如文件,而这里的writeKeysreadKeys的操作对象是Java对象,因为它们的操作对象刚好是“逆向”的,所以才会出现这样的的情况,请读者仔细分析理解。

  这里还有一个容易混淆的内容是sun.misc.Unsafe类,简单讲讲这个类的作用以及代码中出现的INVALID_FIELD_OFFSET的含义:
  参考:http://en.wikipedia.org/wiki/Compare-and-swap
  CAS【Compare And Swap——简单讲它表示比较并交换的算法,属于计算机原子级别的操作,主要用于处理器支持并发提供的原子级操作的测试,通常在计算机的位级别运行该操作,CAS操作包含了三个操作数:内存位置(V)、预期原值(A)和新值(B。如果内存位置的值与预期原值相匹配,则处理器会自动将该位置的值更新成新值,否则处理器不做任何操作。无论哪种情况它都会在CAS指令之前返回该内存位置的值,一些特殊情况下它会返回CAS是否成功的标记,而不会提取这个位置的值。CAS有效地说明了“我认为位置V应该包含值A,如果包含该值则将B放到这个位置,否则不去更改该位置,只是告诉自己这个位置现在的值即可。”
  CAS用于同步方式的时候,会从地址V读取值A、执行多步计算来获得新值B,然后使用CAS将V的值从A更新为B,如果V处的值没有同时更改,则CAS操作成功。类似于CAS的指令允许算法执行读-修改-写操作,而不需要害怕其他线程同时修改变量,因为如果其他线程修改变量则CAS检测它并且失败,该算法可以对操作重新计算。CAS最大的价值是可以在硬件中实现,并且在大多数处理器中是一种轻量级的实现
  Java的并发包java.util.concurrent中大量使用了CAS操作,而涉及到并发的地方都会去调用Unsafe类,也就是说Java语言中针对CAS原子级别的操作都使用sun.misc.Unsafe类实现,这个类中包含了许多native的方法,而且是未开源的,这里就不去了解Unsafe类的源代码了。INVALID_FIELD_OFFSET的值为-1,实际上Java在底层访问一个类的时候,它在调用成员属性时都是用偏移量操作的,而类定义中的第一个字段偏移量应该是0,如果一个类中的字段不匹配或者找不到的情况就返回INVALID_FIELD_OFFSET,即返回-1告诉JVM针对当前这个类执行的字段操作是不合法的。
  FieldReflector的构造函数定义如下:
        FieldReflector(ObjectStreamField[] fields) {
            this.fields = fields;
            int nfields = fields.length;
            readKeys = new long[nfields];
            writeKeys = new long[nfields];
            offsets = new int[nfields];
            typeCodes = new char[nfields];
            ArrayList> typeList = new ArrayList<>();
            Set usedKeys = new HashSet<>();


            for (int i = 0; i < nfields; i++) {
                ObjectStreamField f = fields[i];
                Field rf = f.getField();
                long key = (rf != null) ?
                    unsafe.objectFieldOffset(rf) : Unsafe.INVALID_FIELD_OFFSET;
                readKeys[i] = key;
                writeKeys[i] = usedKeys.add(key) ?
                    key : Unsafe.INVALID_FIELD_OFFSET;
                offsets[i] = f.getOffset();
                typeCodes[i] = f.getTypeCode();
                if (!f.isPrimitive()) {
                    typeList.add((rf != null) ? rf.getType() : null);
                }
            }

            types = typeList.toArray(new Class[typeList.size()]);
            numPrimFields = nfields - types.length;
        }
  FieldReflector主要用来分析成员属性元数据,该类的构造函数参数类型为ObjectStreamFields[],而FieldReflector的构造会初始化它的成员属性,例如readKeys、writeKeys、offsets、typeCodes等。因为FieldReflector需要去分析一个ObjectStreamClass中所有的成员属性信息,这里初始化的时候,其成员属性大部分都是数组类型,那么这里会隐含一个概念:FieldReflector在分析每一个成员属性的时候,使用的方式是索引的统一访问——举个例子:如果它分析的成员属性的索引为offset[3],则对应这个成员属性的其他信息为:readKeys[3]、writeKeys[3]、typeCodes[3]。上边的函数做的就是这样的构建过程,有了这个概念基础读者理解上边源代码就更加容易了。
  本文到这里ObjectStreamClass中的内部类的分析就结束了,接下来带领读者一起去分析ObjectStreamClass主类的相关内容。
  3)native操作:
  ObjectStreamClass中包含了几个本地方法【native的定义,其定义包含如下:
private static native void initNative();
static {
    initNative();
}
private native static boolean hasStaticInitializer(Class cl);
  上边两个方法在源代码中并没定义在一起,分析的时候为了阅读方便就放在一起来解析。
  hasStaticInitializer方法主要用于检测传入的类中是否包含静态初始化方法,如果包含则返回true,反之直接返回false;而initNative()方法的目的就是执行本地方法的“实现初始化”操作,ObjectStreamClass类中的静态初始化块static{}中的内容就是执行initNative()方法【该方法会在ClassLoader载入Java的Class的时候调用initNative方法】——实际上这个地方的initNative方法调用过后,系统底层才会把hasStaticInitializer方法的实现代码初始化,如果不调用initNative方法,当系统在调用hasStaticInitializer方法的时候会因为该方法的实现代码没有初始化而失败,根据Java的语言规定native的方法实现需要使用C语言,所以这里的initNative方法应该是把C语言的实现代码初始化——不过这一切都是我个人的理解,关于这个方法确实没有证据来证明,本文的目的是透析Java序列化,关于native的使用这里就不多说了。
  4)关于SUID
  ObjectStreamClass中定义两部分和SUID有关的内容:本身的SUID和针对被解析类SUID的运算用的成员函数,其自身的serialVersionUID定义如下:
    private static final long serialVersionUID = -6120832682080437368L;
  而针对其被解析类对应的SUID定义如下:
    /** serialVersionUID of represented class (null if not computed yet) */
    private volatile Long suid;
  获得被解析类serialVersionUID的方法如下:
    public long getSerialVersionUID() {
        // REMIND: synchronize instead of relying on volatile?
        if (suid == null) {
            suid = AccessController.doPrivileged(
                new PrivilegedAction() {
                    public Long run() {
                        return computeDefaultSUID(cl);
                    }
                }
            );
        }
        return suid.longValue();
    }
  这个方法的调用会返回被解析类中定义的serialVersionUID的值,如果被解析类不可序列化的,则这个值会返回0L。这个方法会调用Java中的AccessController【访问控制器,它会检查当前的代码是否拥有执行权限,这里的判断主要是查看分析类中是否显示定义了serialVersionUID,如果没有定义则调用computeDefaultSUID方法来计算suid的值。实际上在调用方法getSerialVersionUID方法之前,构造ObjectStreamClass实例的时候会调用方法getDeclaredSUIDsuid赋值,该方法的定义如下:
    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;
    }
  上边方法的代码逻辑很简单:就是获取参数clClass类型中定义的serialVersionUID的值,这个字段的修饰符必须是staticfinal的,因为这个字段是private修饰,所以会调用setAccessible方法解除该字段的访问控制,从而获得该值。关于SUID最后看一下computeDefaultSUID方法的实现:
    private static long computeDefaultSUID(Class cl) {
        if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
        {
            return 0L;
        }

        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            DataOutputStream dout = new DataOutputStream(bout);

            dout.writeUTF(cl.getName());

            int classMods = cl.getModifiers() &
                (Modifier.PUBLIC | Modifier.FINAL |
                 Modifier.INTERFACE | Modifier.ABSTRACT);

            /*
             * compensate for javac bug in which ABSTRACT bit was set for an
             * interface only if the interface declared methods
             */
            Method[] methods = cl.getDeclaredMethods();
            if ((classMods & Modifier.INTERFACE) != 0) {
                classMods = (methods.length > 0) ?
                    (classMods | Modifier.ABSTRACT) :
                    (classMods & ~Modifier.ABSTRACT);
            }
            dout.writeInt(classMods);

            if (!cl.isArray()) {
                /*
                 * compensate for change in 1.2FCS in which
                 * Class.getInterfaces() was modified to return Cloneable and
                 * Serializable for array classes.
                 */
                Class[] interfaces = cl.getInterfaces();
                String[] ifaceNames = new String[interfaces.length];
                for (int i = 0; i < interfaces.length; i++) {
                    ifaceNames[i] = interfaces[i].getName();
                }
                Arrays.sort(ifaceNames);
                for (int i = 0; i < ifaceNames.length; i++) {
                    dout.writeUTF(ifaceNames[i]);
                }
            }

            Field[] fields = cl.getDeclaredFields();
            MemberSignature[] fieldSigs = new MemberSignature[fields.length];
            for (int i = 0; i < fields.length; i++) {
                fieldSigs[i] = new MemberSignature(fields[i]);
            }
            Arrays.sort(fieldSigs, new Comparator() {
                public int compare(MemberSignature ms1, MemberSignature ms2) {
                    return ms1.name.compareTo(ms2.name);
                }
            });
            for (int i = 0; i < fieldSigs.length; i++) {
                MemberSignature sig = fieldSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
                     Modifier.TRANSIENT);
                if (((mods & Modifier.PRIVATE) == 0) ||
                    ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0))
                {
                    dout.writeUTF(sig.name);
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature);
                }
            }

            if (hasStaticInitializer(cl)) {
                dout.writeUTF("");
                dout.writeInt(Modifier.STATIC);
                dout.writeUTF("()V");
            }

            Constructor[] cons = cl.getDeclaredConstructors();
            MemberSignature[] consSigs = new MemberSignature[cons.length];
            for (int i = 0; i < cons.length; i++) {
                consSigs[i] = new MemberSignature(cons[i]);
            }
            Arrays.sort(consSigs, new Comparator() {
                public int compare(MemberSignature ms1, MemberSignature ms2) {
                    return ms1.signature.compareTo(ms2.signature);
                }
            });
            for (int i = 0; i < consSigs.length; i++) {
                MemberSignature sig = consSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL |
                     Modifier.SYNCHRONIZED | Modifier.NATIVE |
                     Modifier.ABSTRACT | Modifier.STRICT);
                if ((mods & Modifier.PRIVATE) == 0) {
                    dout.writeUTF("");
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature.replace('/', '.'));
                }
            }

            MemberSignature[] methSigs = new MemberSignature[methods.length];
            for (int i = 0; i < methods.length; i++) {
                methSigs[i] = new MemberSignature(methods[i]);
            }
            Arrays.sort(methSigs, new Comparator() {
                public int compare(MemberSignature ms1, MemberSignature ms2) {
                    int comp = ms1.name.compareTo(ms2.name);
                    if (comp == 0) {
                        comp = ms1.signature.compareTo(ms2.signature);
                    }
                    return comp;
                }
            });
            for (int i = 0; i < methSigs.length; i++) {
                MemberSignature sig = methSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL |
                     Modifier.SYNCHRONIZED | Modifier.NATIVE |
                     Modifier.ABSTRACT | Modifier.STRICT);
                if ((mods & Modifier.PRIVATE) == 0) {
                    dout.writeUTF(sig.name);
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature.replace('/', '.'));
                }
            }

            dout.flush();

            MessageDigest md = MessageDigest.getInstance("SHA");
            byte[] hashBytes = md.digest(bout.toByteArray());
            long hash = 0;
            for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
                hash = (hash << 8) | (hashBytes[i] & 0xFF);
            }
            return hash;
        } catch (IOException ex) {
            throw new InternalError();
        } catch (NoSuchAlgorithmException ex) {
            throw new SecurityException(ex.getMessage());
        }
    }
  在分析上述的源代码之前读者先回忆一下上个章节中关于Stream Unique Identifier的讲解,在阅读这个方法到时候尽可能结合上一个章节中JVM序列化规范的讲解对比解读,【】符号内的数字表示上一个章节中字节流序列中项说明中的顺序序号,仔细看看该方法生成SUID的流程:
  1. 先判断当前类是否实现了Serializable接口,如果一个类没有实现Serializable接口或者是一个动态代理类,则suid的值为0L
            if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
            {
                return 0L;
            }
  2. 1生成suid的时候,先写入类名,写入的时候以字节方式写入,如果是字符串则使用UTF-8的方式编码;
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    dout.writeUTF(cl.getName());
    
  3. 2然后写入类的修饰符,使用的是一个32-bit的整数,classMods的计算会先进行计算,提取内容修饰符是public、final、interface、abstract的,至于下边代码后半部分代码在注释中有说明是为了处理javac编译的一个bug而存在;
                int classMods = cl.getModifiers() &
                    (Modifier.PUBLIC | Modifier.FINAL |
                     Modifier.INTERFACE | Modifier.ABSTRACT);
                /*
                 * compensate for javac bug in which ABSTRACT bit was set for an
                 * interface only if the interface declared methods
                 */
                Method[] methods = cl.getDeclaredMethods();
                if ((classMods & Modifier.INTERFACE) != 0) {
                    classMods = (methods.length > 0) ?
                        (classMods | Modifier.ABSTRACT) :
                        (classMods & ~Modifier.ABSTRACT);
                }
                dout.writeInt(classMods);
  4. 3按照字典序排列所有实现接口的名称,将这个接口名称写入字节数组,排序使用的方法为Arrays.sort方法,这里会根据clgetInterfaces方法获取所有当前类实现的接口集,然后通过遍历的方式构造一个按照字典序排列的接口名称数组
                if (!cl.isArray()) {
                    /*
                     * compensate for change in 1.2FCS in which
                     * Class.getInterfaces() was modified to return Cloneable and
                     * Serializable for array classes.
                     */
                    Class[] interfaces = cl.getInterfaces();
                    String[] ifaceNames = new String[interfaces.length];
                    for (int i = 0; i < interfaces.length; i++) {
                        ifaceNames[i] = interfaces[i].getName();
                    }
                    Arrays.sort(ifaceNames);
                    for (int i = 0; i < ifaceNames.length; i++) {
                        dout.writeUTF(ifaceNames[i]);
                    }
                }
  5. 4针对类定义中的每一个字段按字典序写入,写入的时候会先写入字段的名称,然后写入32位整数的字段修饰符,最后写入该字段的签名信息,这个地方会使用MemberSignature类用来分析成员属性的信息;在写入的时候会排除statictransient的字段。注意循环内的判断条件——计算mods时,modsMember的修饰进行了两次运算,第一次提取修饰符为public、private、protected、static、final、volatile、transient的内容,然后再判断去掉private、statictransient的内容注意private关键字的出现表示这里提取的信息是非私有的信息
                Field[] fields = cl.getDeclaredFields();
                MemberSignature[] fieldSigs = new MemberSignature[fields.length];
                for (int i = 0; i < fields.length; i++) {
                    fieldSigs[i] = new MemberSignature(fields[i]);
                }
                Arrays.sort(fieldSigs, new Comparator() {
                    public int compare(MemberSignature ms1, MemberSignature ms2) {
                        return ms1.name.compareTo(ms2.name);
                    }
                });
                for (int i = 0; i < fieldSigs.length; i++) {
                    MemberSignature sig = fieldSigs[i];
                    int mods = sig.member.getModifiers() &
                        (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                         Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
                         Modifier.TRANSIENT);
                    if (((mods & Modifier.PRIVATE) == 0) ||
                        ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0))
                    {
                        dout.writeUTF(sig.name);
                        dout.writeInt(mods);
                        dout.writeUTF(sig.signature);
                    }
                }
  6. 5如果当前类中存在静态初始化操作,则写入静态初始化的信息,这里可以看到静态初始化的描述符:JVM序列化规范中提及的段;
                if (hasStaticInitializer(cl)) {
                    dout.writeUTF("");
                    dout.writeInt(Modifier.STATIC);
                    dout.writeUTF("()V");
                }
  7. 6针对非私有构造函数,写入方法名、方法修饰符和方法签名,这里可以看到构造函数初始化的描述符:JVM序列化规范中提及的段——注意方法必须是非私有的,去掉了PRIVATE的内容;
                Constructor[] cons = cl.getDeclaredConstructors();
                MemberSignature[] consSigs = new MemberSignature[cons.length];
                for (int i = 0; i < cons.length; i++) {
                    consSigs[i] = new MemberSignature(cons[i]);
                }
                Arrays.sort(consSigs, new Comparator() {
                    public int compare(MemberSignature ms1, MemberSignature ms2) {
                        return ms1.signature.compareTo(ms2.signature);
                    }
                });
                for (int i = 0; i < consSigs.length; i++) {
                    MemberSignature sig = consSigs[i];
                    int mods = sig.member.getModifiers() &
                        (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                         Modifier.STATIC | Modifier.FINAL |
                         Modifier.SYNCHRONIZED | Modifier.NATIVE |
                         Modifier.ABSTRACT | Modifier.STRICT);
                    if ((mods & Modifier.PRIVATE) == 0) {
                        dout.writeUTF("");
                        dout.writeInt(mods);
                        dout.writeUTF(sig.signature.replace('/', '.'));
                    }
                }
  8. 7针对非私有成员方法,写入方法名、方法修饰符、方法签名,和上一步的逻辑基本相同;
                MemberSignature[] methSigs = new MemberSignature[methods.length];
                for (int i = 0; i < methods.length; i++) {
                    methSigs[i] = new MemberSignature(methods[i]);
                }
                Arrays.sort(methSigs, new Comparator() {
                    public int compare(MemberSignature ms1, MemberSignature ms2) {
                        int comp = ms1.name.compareTo(ms2.name);
                        if (comp == 0) {
                            comp = ms1.signature.compareTo(ms2.signature);
                        }
                        return comp;
                    }
                });
                for (int i = 0; i < methSigs.length; i++) {
                    MemberSignature sig = methSigs[i];
                    int mods = sig.member.getModifiers() &
                        (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                         Modifier.STATIC | Modifier.FINAL |
                         Modifier.SYNCHRONIZED | Modifier.NATIVE |
                         Modifier.ABSTRACT | Modifier.STRICT);
                    if ((mods & Modifier.PRIVATE) == 0) {
                        dout.writeUTF(sig.name);
                        dout.writeInt(mods);
                        dout.writeUTF(sig.signature.replace('/', '.'));
                    }
                }
  9. 8,9最后按照JVM规范中描述的一样,计算serialVersionUID的值,这里的算法比较难理解,就不详细解析算法了,有兴趣的读者可通过示例去理解下边这段代码的执行流程以理解该算法,它对应了前一章节JVM规范部分最后那段代码;
                MessageDigest md = MessageDigest.getInstance("SHA");
                byte[] hashBytes = md.digest(bout.toByteArray());
                long hash = 0;
                for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
                    hash = (hash << 8) | (hashBytes[i] & 0xFF);
                }
                return hash;
  从上述的源代码中可以知道系统自动生成SUID的时候它的生成过程和JVM的序列化规范里定义的过程是一致的。
  5)字段serialPersistentFields和成员属性元数据
  上一个章节中提到过Java中如果一个类具有“可序列化”的语义可实现两个特定的接口,默认情况下若实现了Serializable接口,则statictransitent字段会被识别成默认的可序列化字段【成员属性;但是如果一个类中重定义字段serialPersistentFields,则可序列化字段默认的识别情况会被打破,那么在序列化的时候类中的可序列化字段必须按照serialPersistentFields定义中的数据来识别。
  这个字段的定义如下:
    /** serialPersistentFields value indicating no serializable fields */
    public static final ObjectStreamField[] NO_FIELDS =
        new ObjectStreamField[0];
    private static final ObjectStreamField[] serialPersistentFields =
        NO_FIELDS;
  既然看到了serialPersistentFields的定义,那么看看ObjectStreamClass类中唯一一个使用了该字段的方法getDeclaredSerialFields,这个方法的整体定义如下:
  getDeclaredSerialFields
    private static ObjectStreamField[] getDeclaredSerialFields(Class cl)
        throws InvalidClassException
    {
        ObjectStreamField[] serialPersistentFields = null;
        try {
            Field f = cl.getDeclaredField("serialPersistentFields");
            int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
            if ((f.getModifiers() & mask) == mask) {
                f.setAccessible(true);
                serialPersistentFields = (ObjectStreamField[]) f.get(null);
            }
        } catch (Exception ex) {
        }
        if (serialPersistentFields == null) {
            return null;
        } else if (serialPersistentFields.length == 0) {
            return NO_FIELDS;
        }

        ObjectStreamField[] boundFields =
            new ObjectStreamField[serialPersistentFields.length];
        Set fieldNames = new HashSet<>(serialPersistentFields.length);

        for (int i = 0; i < serialPersistentFields.length; i++) {
            ObjectStreamField spf = serialPersistentFields[i];

            String fname = spf.getName();
            if (fieldNames.contains(fname)) {
                throw new InvalidClassException(
                    "multiple serializable fields named " + fname);
            }
            fieldNames.add(fname);

            try {
                Field f = cl.getDeclaredField(fname);
                if ((f.getType() == spf.getType()) &&
                    ((f.getModifiers() & Modifier.STATIC) == 0))
                {
                    boundFields[i] =
                        new ObjectStreamField(f, spf.isUnshared(), true);
                }
            } catch (NoSuchFieldException ex) {
            }
            if (boundFields[i] == null) {
                boundFields[i] = new ObjectStreamField(
                    fname, spf.getType(), spf.isUnshared());
            }
        }
        return boundFields;
    }
  上边这个方法用来提取实现Serializable接口的类中定的字段信息,如果实现类中定义了serialPersistentFields字段则调用上边的方法来获取该字段中定义的可序列化字段数组【类型ObjectStreamField[]。上述代码中提取字段信息使用了两步serialPersistentFields字段中获取一个字段名的数组,然后通过该字段名去获取字段本身的信息,而且在获取字段到时通过字段名发现重复字段则抛出异常信息。如果实现类中没有定义serialPersistentFields字段则调用下边的方法来获取默认的可序列化字段数组,在获取默认的可序列化字段数组时,statictransient的字段同样会被忽略。
  getDefaultSerialFields
    private static ObjectStreamField[] getDefaultSerialFields(Class cl) {
        Field[] clFields = cl.getDeclaredFields();
        ArrayList 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]);
    }
  上述的两个方法都是从当前类中去获取可序列化成员属性的实现细节,这两个方法会被getSerialFields方法调用。
  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())
        {
            if ((fields = getDeclaredSerialFields(cl)) == null) {
                fields = getDefaultSerialFields(cl);
            }
            Arrays.sort(fields);
        } else {
            fields = NO_FIELDS;
        }
        return fields;
    }
  看完了三个方法的定义,总结一下这三个方法的实现细节以了解系统通过什么方式来获取成员属性的元数据信息:
  1. 在提取成员属性【字段的元数据时,会先调用getSerialFields方法从一个类中获取可序列化字段元数据数组信息,这个方法会去调用前两个方法获取成员属性的元数据信息,其判断条件要求这个类满足下边条件:
    -- 该类必须实现了Serializable接口;
    -- 该类不能实现Externalizable接口;
    -- 该类不能是一个Java的动态代理类
    -- 该类不能是一个接口
  2. 在该类获取元数据的调用过程中,该方法会先调用getDeclaredSerialFields上述代码中第一个方法,当调用这个方法返回的值为null,也就是该类中没有定义serialPersistentFields字段的时候,就调用getDefaultSerialFields上述代码中第二个方法来获取ObjectStreamField[]数组;
  3. getDefaultSerialFields的方法就是一步到位,直接使用ArrayList类型的容器list获取该类中statictransitent的字段元数据集合信息,最后将list转化为一个ObjectStreamField[]类型;
  4. getDeclareSerialFields的方法主要包含两个步骤:先从传入的类中获取serialPersistentFields字段信息,如果serialPersistentFields中定义了重名成员属性则抛出InvalidClassException异常信息,内部循环中有一个检测重复字段的代码段;随后代码会通过字段名规范化地获取的字段信息,此规范化的过程通过遍历获取的ObjectStreamField[]数组来生成一个新的ObjectStreamField[]类型的数组返回;
  这个地方可以看到ObjectStreamField二次构造过程,前边解析这个类的时候已经提到了,ObjectStreamField的构造函数有4个,只有1个使用了完整的构造流程,看看上边代码内部实例化ObjectStreamField的过程:
  getDeclaredSerialFields中的ObjectStreamField实例化:
new ObjectStreamField(f, spf.isUnshared(), true);
  getDefaultSerialFieldsObjectStreamField的实例化:
new ObjectStreamField(clFields[i], false, true)
  上述两个类中创建ObjectStreamField实例的时候第一个参数的类型都是Field,证明它执行了二次构造过程,调用了default域中包含了完整构造流程的构造函数
  6)getField和字节流交互方法
  前一个章节的代码解析已经解析了三个被解析类中的定义的获取可序列化成员属性的方法,ObjectStreamClass还有针对所有字段的获取方法集:
    ObjectStreamField[] getFields(boolean copy) {
        return copy ? fields.clone() : fields;
    }
    ObjectStreamField getField(String name, Class type) {
        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i];
            if (f.getName().equals(name)) {
                if (type == null ||
                    (type == Object.class && !f.isPrimitive()))
                {
                    return f;
                }
                Class ftype = f.getType();
                if (ftype != null && type.isAssignableFrom(ftype)) {
                    return f;
                }
            }
        }
        return null;
    }
    public ObjectStreamField[] getFields() {
        return getFields(true);
    }
    public ObjectStreamField getField(String name) {
        return getField(name, null);
    }
  上述字段中获取被解析类的字段方法分为两种类型:getFieldsgetField方法,这两个方法都各自有一个重载方法;
  getFields——该方法主要用于获取所有的成员属性元数据数组信息default域getFields方法中的copy参数告诉JVM是否要创建一个成员属性元数据数组的副本【拷贝】
  getField——该方法主要用于根据属性名称获取单个成员属性的元数据信息default域getField方法中第二个参数告诉JVM是否要创建一个成员属性元数据数组的副本【拷贝】
  上述内部类FieldReflector中包含了四个特殊的方法,这四个方法的详细信息前文介绍过,这里不介绍了,在ObjectStreamClass中同样包含了这四个方法,这四个方法会调用成员fieldReflFieldReflector类型中的四个对应方法,其定义如下:
    void getPrimFieldValues(Object obj, byte[] buf) {
        fieldRefl.getPrimFieldValues(obj, buf);
    }
    void setPrimFieldValues(Object obj, byte[] buf) {
        fieldRefl.setPrimFieldValues(obj, buf);
    }
    void getObjFieldValues(Object obj, Object[] vals) {
        fieldRefl.getObjFieldValues(obj, vals);
    }
    void setObjFieldValues(Object obj, Object[] vals) {
        fieldRefl.setObjFieldValues(obj, vals);
    }
  7)成员属性:
  ObjectStreamClass中所有的成员属性上边只解析了一个suid,该属性表示被解析类的serialVersionUID字段信息,接下来看看剩余的成员属性信息:
  ---------------------被解析类的基本信息-----------------------
    /** class associated with this descriptor (if any) */
    private Class cl;
    /** name of class represented by this descriptor */
    private String name;
    /** serialVersionUID of represented class (null if not computed yet) */
    private volatile Long suid;
  上述三个成员属性为被解析类的基本描述信息:
  • cl——java.lang.Class
    成员属性主要用于解析类过程中提取类的元数据信息,因为涉及到“反射”的用法,所以该类型对应一个Class类型;
  • name——java.lang.String
    成员属性表示被解析类的类名信息
  • suid——java.lang.Long
    成员属性表示被解析类在序列化过程中的SUID
  ---------------------类的SC_*标识----------------------
  在前一个章节可知道Java序列化中有五个类级别的标记:SC_WRITE_METHOD、SC_BLOCKDATA、SC_SERIALIZABLE、SC_EXTERNALIZABLE、SC_ENUM,这几个标记虽然不会写入到字节流中,但这五个标记会在执行序列化和反序列化的过程中判断一个类使用什么方式处理,该标记在ObjectStreamClass中的定义如下:
    /** true if represents dynamic proxy class */
    private boolean isProxy;
    /** true if represents enum type */
    private boolean isEnum;
    /** true if represented class implements Serializable */
    private boolean serializable;
    /** true if represented class implements Externalizable */
    private boolean externalizable;
    /** true if desc has data written by class-defined writeObject method */
    private boolean hasWriteObjectData;
    /**
     * true if desc has externalizable data written in block data format; this
     * must be true by default to accommodate ObjectInputStream subclasses which
     * override readClassDescriptor() to return class descriptors obtained from
     * ObjectStreamClass.lookup() (see 4461737)
     */
    private boolean hasBlockExternalData = true;
  • isProxy——boolean
    该标记判断当前类是否是一个Java中的动态代理类
  • isEnum——boolean
    该标记判断当前类是否是一个Java中的Enum枚举类
  • serializable——boolean
    该标记判断当前类是否实现了Serializable接口;
  • externalizable——boolean
    该标记判断当前类是否实现了Externalizable接口;
  • hasWriteObjectData——boolean
    该标记判断当前类是否重写了writeObject方法,如果写入了则需要标记当前类为SC_WRITE_METHOD类型;
  • hasBlockExternalData——boolean
    该标记和SC_*的五个标记无关,它只是告诉系统当前解析的类会有额外的信息写入到字节流中;
  ---------------------该类包含的异常信息----------------------
  ObjectStreamClass中拥有一个ExceptionInfo的内部类,主要用于描述类的元数据解析中的异常信息,看看下边和异常相关的成员属性:
    /** exception (if any) thrown while attempting to resolve class */
    private ClassNotFoundException resolveEx;
    /** exception (if any) to throw if non-enum deserialization attempted */
    private ExceptionInfo deserializeEx;
    /** exception (if any) to throw if non-enum serialization attempted */
    private ExceptionInfo serializeEx;
    /** exception (if any) to throw if default serialization attempted */
    private ExceptionInfo defaultSerializeEx;
  • resolveEx——java.lang.ClassNotFoundException
    ObjectStreamClass解析一个类的元数据时,如果解析的类在类加载器中找不到,则需要抛出该异常信息
  • deserializeEx——java.io.ObjectStreamClass.ExceptionInfo
    当一个Enum枚举类型的Java对象被反序列化的时候,若出现异常则将异常信息保存在deserializeEx成员属性中;
  • serializeEx——java.io.ObjectStreamClass.ExceptionInfo
    当一个Enum枚举类型的Java对象被序列化的时候,若出现异常则将异常信息保存在serializeEx成员属性中;
  • defaultSerializeEx——java.io.ObjectStreamClass.ExceptionInfo
    在执行默认序列化的时候,如果出现了异常信息则异常信息保存在defaultSerializeEx成员属性中;
  ----------------------字段、类数据信息----------------------
    /** serializable fields */
    private ObjectStreamField[] fields;
    /** aggregate marshalled size of primitive fields */
    private int primDataSize;
    /** number of non-primitive fields */
    private int numObjFields;
    /** reflector for setting/getting serializable field values */
    private FieldReflector fieldRefl;
    /** data layout of serialized objects described by this class desc */
    private volatile ClassDataSlot[] dataLayout;
  • fields——java.io.ObjectStreamField[]
    当前ObjectStreamClass中包含的所有成员属性元数据信息,上一个小章节中解析的三个和字段相关的方法其目的就是为了生成当前解析类的字段元数据数组;
  • primDataSize——int
    当前解析类中所有基础类型成员属性所需要的数据大小;
  • numObjFields——int
    当前解析类中所有基础类型的成员属性的数量;
  • fieldRefl——java.io.ObjectStreamClass.FieldReflector
    创建一个字段分析器,用于分析被解析类中所有成员属性【字段的相关元数据信息
  • dataLayout——java.io.ObjectStreamClass.ClassDataSlot[]
    创建一个类描述符相关到ClassDataSlot类型的数组;
  ----------------------类中构造函数、特殊方法----------------------
  ObjectStreamClass中包含了被解析类中构造函数和定义的几个特殊可重写方法的成员函数信息:
    /** serialization-appropriate constructor, or null if none */
    private Constructor cons;
    /** class-defined writeObject method, or null if none */
    private Method writeObjectMethod;
    /** class-defined readObject method, or null if none */
    private Method readObjectMethod;
    /** class-defined readObjectNoData method, or null if none */
    private Method readObjectNoDataMethod;
    /** class-defined writeReplace method, or null if none */
    private Method writeReplaceMethod;
    /** class-defined readResolve method, or null if none */
    private Method readResolveMethod;
  • cons——java.lang.reflect.Constructor
    被解析类的构造函数信息;
  • writeObjectMethod——java.lang.reflect.Method
    被解析类如果重写writeObject方法则该成员属性是重写过的writeObject方法的元数据信息
  • readObjectMethod——java.lang.reflect.Method
    被解析类如果重写readObject方法则该成员属性是重写过的readObject方法的元元数据信息
  • readObjectNoDataMethod——java.lang.reflect.Method
    被解析类如果重写readObjectNoData方法则该成员属性是重写过的readObjectNoData方法的元数据信息
  • writeReplaceMethod——java.lang.reflect.Method
    被解析类如果重写writeReplace方法则该成员属性是重写过的writeReplace方法的元数据信息
  • readResolveMethod——java.lang.reflect.Method
    被解析类如果重写readResolve方法则该成员属性是重写过的readResolve方法的元数据信息
  ----------------------类中描述信息----------------------
  ObjectStreamClass中还定义了两个成员,该成员表示类被解析类的类描述信息:
    /** local class descriptor for represented class (may point to self) */
    private ObjectStreamClass localDesc;
    /** superclass descriptor appearing in stream */
    private ObjectStreamClass superDesc;
  • localDesc——java.io.ObjectStreamClass
    该类描述信息表示被解析类元数据信息,它可能直接引用自己【因为当前ObjectStreamClass描述的就是被解析类的元数据信息】
  • superDesc——java.io.ObjectStreamClass
    该类描述信息表示被解析类的父类元数据信息
  8)Java Bean规范方法:
  这部分枚举了ObjectStreamClass中所有遵循了Java Bean规范的方法,设置/获取函数
  Get方法定义清单
  【*:注意Java Bean规范中boolean数据的Get方法的方法命名,而且该类中有些方法不符合Java Bean规范,本文中提到的Java Bean规范方法主要指没有太复杂的逻辑的Get方法以及Get方法的变种,把这些方法移除过后,针对这个类中其他源代码的阅读就简单多了。】
    public String getName() {
        return name;
    }
    public Class forClass() {
        return cl;
    }
    ObjectStreamClass getSuperDesc() {
        return superDesc;
    }
    ObjectStreamClass getLocalDesc() {
        return localDesc;
    }
    boolean isProxy() {
        return isProxy;
    }
    boolean isEnum() {
        return isEnum;
    }
    boolean isExternalizable() {
        return externalizable;
    }
    boolean isSerializable() {
        return serializable;
    }
    boolean hasBlockExternalData() {
        return hasBlockExternalData;
    }
    boolean hasWriteObjectData() {
        return hasWriteObjectData;
    }
    boolean hasWriteObjectMethod() {
        return (writeObjectMethod != null);
    }
    boolean hasReadObjectMethod() {
        return (readObjectMethod != null);
    }
    boolean hasReadObjectNoDataMethod() {
        return (readObjectNoDataMethod != null);
    }
    boolean hasWriteReplaceMethod() {
        return (writeReplaceMethod != null);
    }
    boolean hasReadResolveMethod() {
        return (readResolveMethod != null);
    }
    boolean isInstantiable() {
        return (cons != null);
    }
    int getPrimDataSize() {
        return primDataSize;
    }
    int getNumObjFields() {
        return numObjFields;
    }
    ClassNotFoundException getResolveException() {
        return resolveEx;
    }
  上边所有方法中,注意几个Method后缀方法,这些方法内部会判断Method后缀的成员是否为空,实际上这些方法主要用于判断被解析类是否重写了几个特殊的方法:writeObject、readObject、readObjectNoData、writeReplace、readResolve,同样的针对构造函数cons成员的判断也同样如此,如果为空则表示被解析类中没有定义这些信息。
  9)序列化检测方法
  ObjectStreamClass类中定义了三个序列化检测方法:
    void checkDeserialize() throws InvalidClassException {
        if (deserializeEx != null) {
            throw deserializeEx.newInvalidClassException();
        }
    }
    void checkSerialize() throws InvalidClassException {
        if (serializeEx != null) {
            throw serializeEx.newInvalidClassException();
        }
    }
    void checkDefaultSerialize() throws InvalidClassException {
        if (defaultSerializeEx != null) {
            throw defaultSerializeEx.newInvalidClassException();
        }
    }
  上边三个方法主要用于检测被解析类的信息:
  • checkDeserialize
    如果被解析的类不支持反序列化,则该方法会抛出一个InvalidClassException异常信息,但是该方法不针对Enum枚举类型
  • checkSerialize
    如果被解析的类不支持序列化,则该方法会抛出一个InvalidClassException异常信息,该方法同样不针对Enum枚举类型
  • checkDefaultSerialize
    如果被解析的类不支持使用Java中的默认序列化机制,则会抛出一个InvalidClassException异常信息,该方法不针对Enum枚举类型
  10)构造函数、成员函数
  ObjectStreamClass在解析一个类的时候,有四个专有方法来获得被解析类的构造函数成员函数,它们的定义如下:
  构造函数的获取:
  getExternalizableConstructor、getSerializableConstructor
    private static Constructor getExternalizableConstructor(Class cl) {
        try {
            Constructor cons = cl.getDeclaredConstructor((Class[]) null);
            cons.setAccessible(true);
            return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
                cons : null;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
    private static Constructor getSerializableConstructor(Class cl) {
        Class initCl = cl;
        while (Serializable.class.isAssignableFrom(initCl)) {
            if ((initCl = initCl.getSuperclass()) == null) {
                return null;
            }
        }
        try {
            Constructor cons = initCl.getDeclaredConstructor((Class[]) null);
            int mods = cons.getModifiers();
            if ((mods & Modifier.PRIVATE) != 0 ||
                ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
                 !packageEquals(cl, initCl)))
            {
                return null;
            }
            cons = reflFactory.newConstructorForSerialization(cl, cons);
            cons.setAccessible(true);
            return cons;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
  上述方法中getExternalizableConstructor方法用于获取被解析类的public无参构造函数,这个函数可针对所有的类获取它的public无参构造函数,但是从方法定义的名字上看主要是为了处理外部化的类。而getSerializableConstructor方法用于获取被解析类的无参构造函数,这个方法会从当前被解析类不断递归检索直到第一个不可序列化的父类为止。在构造函数的获取中系统调用了一个上边未提及的静态变量:
    /** reflection factory for obtaining serialization constructors */
    private static final ReflectionFactory reflFactory =
        AccessController.doPrivileged(
            new ReflectionFactory.GetReflectionFactoryAction());
  这个变量的类型是sun.reflect.ReflectionFactory类型,它在初始化的时候通过访问控制器检查了代码的执行权限,这个类可以通过不调用构造函数的方式去实例化一个对象,后边解析序列化流程的时候再来看里面的代码细节,从其含义上讲,需要“不通过构造函数”创建对象的情况会在反序列化中使用,所以这里的ReflectionFactory类很有可能是在反序列化通过对象的状态数据重建对象的时候使用,也可以这样理解Java的序列化机制在处理可序列化的类时不会调用可序列化类的构造函数。
  成员函数的获取:
  getInheritableMethod、getPrivateMethod
    private static Method getInheritableMethod(Class cl, String name,
                                               Class[] argTypes,
                                               Class returnType)
    {
        Method meth = null;
        Class defCl = cl;
        while (defCl != null) {
            try {
                meth = defCl.getDeclaredMethod(name, argTypes);
                break;
            } catch (NoSuchMethodException ex) {
                defCl = defCl.getSuperclass();
            }
        }

        if ((meth == null) || (meth.getReturnType() != returnType)) {
            return null;
        }
        meth.setAccessible(true);
        int mods = meth.getModifiers();
        if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
            return null;
        } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
            return meth;
        } else if ((mods & Modifier.PRIVATE) != 0) {
            return (cl == defCl) ? meth : null;
        } else {
            return packageEquals(cl, defCl) ? meth : null;
        }
    }
    private static Method getPrivateMethod(Class cl, String name,
                                           Class[] argTypes,
                                           Class returnType)
    {
        try {
            Method meth = cl.getDeclaredMethod(name, argTypes);
            meth.setAccessible(true);
            int mods = meth.getModifiers();
            return ((meth.getReturnType() == returnType) &&
                    ((mods & Modifier.STATIC) == 0) &&
                    ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
  上述两个方法用于解析类并且获取其成员函数,getPrivateMethod方法用于获取静态私有成员方法getInheritableMethod方法用于获取静态、抽象的成员方法。这个成员函数有四个参数:
  • cl——java.lang.Class
    该成员函数所属的类
  • name——java.lang.String
    该成员函数的名称
  • argTypes——java.lang.Class[]
    该成员函数的参数表
  • returnType——java.lang.Class
    该成员函数的返回值
  简单看看上述函数的执行细节:
  1. 函数在执行过程会先去获取成员函数的信息,返回一个Method类型的引用;
  2. 然后该函数会检查函数的返回值以确认得到的Method引用指向的成员函数是否匹配的;
  3. 其次该函数会打开访问控制meth.setAccessible(true);
  4. 其实上述成员函数的返回部分比较复杂,它分为以下几种情况:
    -- 如果该方法是staticabstract修饰的,则返回null
    -- 如果该方法是publicprotected修饰的,则返回方法本身
    -- 如果该方法是private的,并且传入cldefCl表示的类引用相等,则返回该私有方法,否则就返回null
    -- 其他情况则对比该方法所属类的包名是否相同,若相同则返回获取的成员函数,否则返回null
  理解了getInheritableMethod方法的详细流程过后,再来理解getPrivateMethod方法就更加容易了,这里就不解析getPrivateMethod方法的源代码了。
  11)调用特殊方法
  ObjectStreamClass定义了五个调用特殊方法的成员函数,主要负责调用被解析类中重写过的五个特殊方法。
  invokeWriteObject——调用writeObject方法:
    void invokeWriteObject(Object obj, ObjectOutputStream out)
        throws IOException, UnsupportedOperationException
    {
        if (writeObjectMethod != null) {
            try {
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  看看invokeWriteObject方法,该方法调用了ObjectStreamClass成员属性writeObjectMethod,这个成员属性会引用被解析类中重写过writeObject方法,writeObject方法的参数为ObjectOutputStream,除了成员属性writeObjectMethod以外,invokeWriteObject方法包含了下边两个参数:
  • obj——java.lang.Object
    该参数表示需要调用writeObject成员函数的对象,即为一个重写了writeObject方法的类对应的实例;
  • out——java.io.ObjectOutputStream
    该参数会作为writeObject成员函数的参数执行传递;
  该方法在调用writeObject方法的时候注意其异常的抛出
  • 如果成员属性writeObjectMethod方法为空,则抛出UnsupportedOperationException异常;
  • 如果调用方法过程出现了错误则抛出InvocationTargetException异常,这种情况需要判断是IOException还是其他异常信息,其他异常使用辅助函数throwMiscException处理;
  • 如果调用方法是无法通过访问控制的检测则抛出IllegalAccessException异常,这个时候该方法会抛出一个InternalError的错误信息;
  这里解析了writeObject方法的执行细节,有了这个执行细节,这里就没有必要解析后边四个方法的执行细节了。
  invokeReadObject——调用readObject方法:
    void invokeReadObject(Object obj, ObjectInputStream in)
        throws ClassNotFoundException, IOException,
               UnsupportedOperationException
    {
        if (readObjectMethod != null) {
            try {
                readObjectMethod.invoke(obj, new Object[]{ in });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ClassNotFoundException) {
                    throw (ClassNotFoundException) th;
                } else if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  invokeReadObjectNoData——调用readObjectNoData方法:
    void invokeReadObjectNoData(Object obj)
        throws IOException, UnsupportedOperationException
    {
        if (readObjectNoDataMethod != null) {
            try {
                readObjectNoDataMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  invokeWriteReplace——调用writeReplace方法:
    Object invokeWriteReplace(Object obj)
        throws IOException, UnsupportedOperationException
    {
        if (writeReplaceMethod != null) {
            try {
                return writeReplaceMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError();  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  invokeReadResolve——调用readResolve方法:
    Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError();  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  12)辅助方法
  因为后边需要分析的方法越来越接近解析类的元数据相关的逻辑,所以这里先介绍该类中定义的几个重要的辅助方法,这些辅助方法在该类中会经常性被调用。
  processQueue方法——该方法主要用于移除ReferenceQueue队列中的引用,从其参数定义可以知道主要用于操作Caches类中的成员,这个方法有两个参数:
  • queue——java.lang.ref.ReferenceQueue>
    该参数一般会传入一个队列类型的数据结构,Caches中有两个成员是“Queue”类型;
  • map——java.util.concurrent.ConcurrentMapextends java.lang.ref.WeakReference>,?>
    该参数表示一个缓存的结构,这个结构和Caches中定义的另外两个缓存成员是一致的;
    static void processQueue(ReferenceQueue> queue,
                             ConcurrentMap>, ?> map)
    {
        Reference> ref;
        while((ref = queue.poll()) != null) {
            map.remove(ref);
        }
    }
  throwMiscException方法——该方法是一个用于抛出异常信息的方法,主要抛出三个特殊异常:RuntimeException、Error、IOException异常,其定义如下:
    private static void throwMiscException(Throwable th) throws IOException {
        if (th instanceof RuntimeException) {
            throw (RuntimeException) th;
        } else if (th instanceof Error) {
            throw (Error) th;
        } else {
            IOException ex = new IOException("unexpected exception type");
            ex.initCause(th);
            throw ex;
        }
    }
  classNamesEqual方法——该方法用于检查传入的两个类名是否相等,需要注意的是这个方法检查的是类名而非类全名,即不是检查包含了包名的类全名信息,其定义如下:
    private static boolean classNamesEqual(String name1, String name2) {
        name1 = name1.substring(name1.lastIndexOf('.') + 1);
        name2 = name2.substring(name2.lastIndexOf('.') + 1);
        return name1.equals(name2);
    }
  packageEquals方法——该方法用于检测传入的两个Class的实例的包名是否相等,相等返回true,否则返回false,其定义如下:
    private static boolean packageEquals(Class cl1, Class cl2) {
        return (cl1.getClassLoader() == cl2.getClassLoader() &&
                getPackageName(cl1).equals(getPackageName(cl2)));
    }
  getPackageName方法——该方法用于获取传入的Class实例的包名,前边一个方法就调用了该成员函数,其定义如下:
    private static String getPackageName(Class cl) {
        String s = cl.getName();
        int i = s.lastIndexOf('[');
        if (i >= 0) {
            s = s.substring(i + 2);
        }
        i = s.lastIndexOf('.');
        return (i >= 0) ? s.substring(0, i) : "";
    }
  computeFieldOffsets方法——该方法用于计算以及设置可序列化字段的偏移量,其定义如下:
    private void computeFieldOffsets() throws InvalidClassException {
        primDataSize = 0;
        numObjFields = 0;
        int firstObjIndex = -1;

        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i];
            switch (f.getTypeCode()) {
                case 'Z':
                case 'B':
                    f.setOffset(primDataSize++);
                    break;

                case 'C':
                case 'S':
                    f.setOffset(primDataSize);
                    primDataSize += 2;
                    break;

                case 'I':
                case 'F':
                    f.setOffset(primDataSize);
                    primDataSize += 4;
                    break;

                case 'J':
                case 'D':
                    f.setOffset(primDataSize);
                    primDataSize += 8;
                    break;

                case '[':
                case 'L':
                    f.setOffset(numObjFields++);
                    if (firstObjIndex == -1) {
                        firstObjIndex = i;
                    }
                    break;

                default:
                    throw new InternalError();
            }
        }
        if (firstObjIndex != -1 &&
            firstObjIndex + numObjFields != fields.length)
        {
            throw new InvalidClassException(name, "illegal field order");
        }
    }
  关于偏移量的计算需要注意数据类型,偏移量的计算会改变成员属性primDataSizenumObjFields的大小,如果遇到一个基础类型primDataSize会根据其类型所占字节数进行统计,如果遇到非基础类型的数据则numObjFields的大小也会递增。看看每种数据的计算规则:
  • boolean【Z】、byte【B
    ——占用1个字节,primDataSize每次增加1numObjFields不变
  • char【C】、short【S
    ——占用2个字节,primDataSize每次增加2numObjFields不变
  • int【I】、float【F
    ——占用4个字节,primDataSize每次增加4numObjFields不变
  • long【J】、double【D
    ——占用8个字节,primDataSize每次增加8numObjFields不变
  • 数组array【[】、对象object【L
    ——primDataSize不变numObjFields每次增加1
  toString方法——这个方法不讲解了,读者直接看它的定义就明白了。
    public String toString() {
        return name + ": static final long serialVersionUID = " +
            getSerialVersionUID() + "L;";
    }
  getVariantFor方法——该方法用于获取当前ObjectStreamClass的实例引用,如果传入类和当前实例中解析的类相等,则返回当前ObjectStreamClass,否则需要通过传入的clClass去生成一个新的ObjectStreamClass实例,其定义如下:
    private ObjectStreamClass getVariantFor(Class cl)
        throws InvalidClassException
    {
        if (this.cl == cl) {
            return this;
        }
        ObjectStreamClass desc = new ObjectStreamClass();
        if (isProxy) {
            desc.initProxy(cl, null, superDesc);
        } else {
            desc.initNonProxy(this, cl, null, superDesc);
        }
        return desc;
    }
  getReflector方法——该成员函数通过字段元数据数组【ObjectStreamField[]类型和当前类的元数据【ObjectStreamClass类型获取一个FieldReflector,该FieldReflector实例会匹配传入的字段元数据数组和传入的类中的字段元数据数组是否一致,如果不一致要么返回字段默认值,要么放弃提供的值。实际上在Java的序列化规范中,有很多规则是使用默认值填充成员属性【字段的数据信息,那么FieldReflector处理的就是基于成员属性的基本比较,它会告诉JVM是应该直接使用提供值还是使用默认值或者索性放弃,它影响了字段基本的序列化和反序列化的策略。
  该方法定义如下:
    private static FieldReflector getReflector(ObjectStreamField[] fields,
                                               ObjectStreamClass localDesc)
        throws InvalidClassException
    {
        // class irrelevant if no fields
        Class cl = (localDesc != null && fields.length > 0) ?
            localDesc.cl : null;
        processQueue(Caches.reflectorsQueue, Caches.reflectors);
        FieldReflectorKey key = new FieldReflectorKey(cl, fields,
                                                      Caches.reflectorsQueue);
        Reference ref = Caches.reflectors.get(key);
        Object entry = null;
        if (ref != null) {
            entry = ref.get();
        }
        EntryFuture future = null;
        if (entry == null) {
            EntryFuture newEntry = new EntryFuture();
            Reference newRef = new SoftReference<>(newEntry);
            do {
                if (ref != null) {
                    Caches.reflectors.remove(key, ref);
                }
                ref = Caches.reflectors.putIfAbsent(key, newRef);
                if (ref != null) {
                    entry = ref.get();
                }
            } while (ref != null && entry == null);
            if (entry == null) {
                future = newEntry;
            }
        }

        if (entry instanceof FieldReflector) {  // check common case first
            return (FieldReflector) entry;
        } else if (entry instanceof EntryFuture) {
            entry = ((EntryFuture) entry).get();
        } else if (entry == null) {
            try {
                entry = new FieldReflector(matchFields(fields, localDesc));
            } catch (Throwable th) {
                entry = th;
            }
            future.set(entry);
            Caches.reflectors.put(key, new SoftReference(entry));
        }

        if (entry instanceof FieldReflector) {
            return (FieldReflector) entry;
        } else if (entry instanceof InvalidClassException) {
            throw (InvalidClassException) entry;
        } else if (entry instanceof RuntimeException) {
            throw (RuntimeException) entry;
        } else if (entry instanceof Error) {
            throw (Error) entry;
        } else {
            throw new InternalError("unexpected entry: " + entry);
        }
    } 
         
        
  matchFields方法——上述FieldReflector获取过程中调用了此方法对本地类中字段元数据和传入的字段元数据直接比较,这里不重复,此方法的定义如下:
    private static ObjectStreamField[] matchFields(ObjectStreamField[] fields,
                                                   ObjectStreamClass localDesc)
        throws InvalidClassException
    {
        ObjectStreamField[] localFields = (localDesc != null) ?
            localDesc.fields : NO_FIELDS;

        /*
         * Even if fields == localFields, we cannot simply return localFields
         * here.  In previous implementations of serialization,
         * ObjectStreamField.getType() returned Object.class if the
         * ObjectStreamField represented a non-primitive field and belonged to
         * a non-local class descriptor.  To preserve this (questionable)
         * behavior, the ObjectStreamField instances returned by matchFields
         * cannot report non-primitive types other than Object.class; hence
         * localFields cannot be returned directly.
         */

        ObjectStreamField[] matches = new ObjectStreamField[fields.length];
        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i], m = null;
            for (int j = 0; j < localFields.length; j++) {
                ObjectStreamField lf = localFields[j];
                if (f.getName().equals(lf.getName())) {
                    if ((f.isPrimitive() || lf.isPrimitive()) &&
                        f.getTypeCode() != lf.getTypeCode())
                    {
                        throw new InvalidClassException(localDesc.name,
                            "incompatible types for field " + f.getName());
                    }
                    if (lf.getField() != null) {
                        m = new ObjectStreamField(
                            lf.getField(), lf.isUnshared(), false);
                    } else {
                        m = new ObjectStreamField(
                            lf.getName(), lf.getSignature(), lf.isUnshared());
                    }
                }
            }
            if (m == null) {
                m = new ObjectStreamField(
                    f.getName(), f.getSignature(), false);
            }
            m.setOffset(f.getOffset());
            matches[i] = m;
        }
        return matches;
    }
  newInstance方法——这个方法相信读者都不会陌生,只是需要知道这里的newInstance创建的实例不是ObjectStreamClass的实例,而是被解析类的实例,也就是说当它去解析某个类获取类的元数据时,这个方法创建的是被解析类的实例,创建方式是调用被解析类的构造函数,和前边sun.reflect.ReflectionFactory的创建方式不一样。它的调用规则如下:
  • 如果被解析的类是实现了Externalizable接口可外部化的一个类,则调用它的public无参构造函数
  • 如果被解析的类是实现了Serializable接口可序列化的一个类,则调用先调用它的父类无参构造函数直到当前类实现递归调用
  • 如果当前的类对应元数据信息和关联类不匹配则抛出UnsupportedOperationException异常;
  • 如果被解析的类不具有“可序列化”的语义,或者它的无参构造函数不能调用,则同样抛出UnsupportedOperationException异常;
  该方法的定义如下:
    Object newInstance()
        throws InstantiationException, InvocationTargetException,
               UnsupportedOperationException
    {
        if (cons != null) {
            try {
                return cons.newInstance();
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError();
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  13)ClassDataSlot相关
  在ObjectStreamClass分析过程中,遇到了一个ClassDataSlot类,这个类具体做什么用呢?先看看和它相关的两个成员函数getClassDataLayoutgetClassDataLayout0定义,然后来看看它里面的结构。
  getClassDataLayout方法定义如下:
    ClassDataSlot[] getClassDataLayout() throws InvalidClassException {
        // REMIND: synchronize instead of relying on volatile?
        if (dataLayout == null) {
            dataLayout = getClassDataLayout0();
        }
        return dataLayout;
    }
  getClassDataLayout0方法定义如下:
    private ClassDataSlot[] getClassDataLayout0()
        throws InvalidClassException
    {
        ArrayList slots = new ArrayList<>();
        Class start = cl, end = cl;

        // locate closest non-serializable superclass
        while (end != null && Serializable.class.isAssignableFrom(end)) {
            end = end.getSuperclass();
        }

        for (ObjectStreamClass d = this; d != null; d = d.superDesc) {

            // search up inheritance hierarchy for class with matching name
            String 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;
                }
            }

            // 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);
        return slots.toArray(new ClassDataSlot[slots.size()]);
    }
  上述两个方法都和ClassDataSlot相关,至于getClassDataLayout类不用讲解,它调用的就是getClassDataLayout0方法,看看getClassDataLayout0方法究竟做了些什么。实际上这个类的含义很简单,本文解析了序列化一个Java对象的顺序,而且在序列化输出一个Java对象的类描述符的时候,不知读者是否还记得它的顺序是从顶级父类 -> 当前类,而ClassDataSlot内部类中核心属性就是desc【ObjectStreamClass,那么答案就出来了:getClassDataLayout0递归获取当前序列化对象的父类元数据信息直到当前类,并且把所有元数据信息保存在一个数组中,这个数组中的元素是ClassDataSlot类型,该类型使用“组合”方式封装ObjectStreamClass类型。实际上正因为有了这两个方法,在序列化一个对象的时候才能实现针对它的顶级父类到当前类的类的元数据的从上至下递归序列化
  14)look*方法:
  look*方法主要有三个,其中有两个是外部可使用的ObjectStreamClassAPI,这个方法主要用于分析传入的类获取传入类ObjectStreamClass实例传入类的元数据信息ObjectStreamClass不仅仅可以分析构造函数传入类生成一个传入类的元数据信息——也就是自身类【成员属性cl的元数据信息,而且可以通过lookup成员API去分析任何一个类获取任何一个类的ObjectStreamClass实例以取得传入类的元数据信息。先看看lookup方法的代码定义:
  lookup、lookupAny
    public static ObjectStreamClass lookup(Class cl) {
        return lookup(cl, false);
    }
    public static ObjectStreamClass lookupAny(Class cl) {
        return 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);
        Reference ref = Caches.localDescs.get(key);
        Object entry = null;
        if (ref != null) {
            entry = ref.get();
        }
        EntryFuture future = null;
        if (entry == null) {
            EntryFuture newEntry = new EntryFuture();
            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 (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 {
                entry = new ObjectStreamClass(cl);
            } catch (Throwable th) {
                entry = th;
            }
            if (future.set(entry)) {
                Caches.localDescs.put(key, new SoftReference(entry));
            } else {
                // nested lookup call already set future
                entry = 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);
        }
    } 
          
         
  look*方法对外的两个API【lookuplookupAny实际上也就是调用了默认default域中的方法,所以只要理解了default域lookup方法就等于理解了ObjectStreamClass中的look*方法,看看下边的代码流程:
  1. 先检查传入的类是否支持序列化,第二个参数告诉ObjectStreamClass实例是否需要检测传入类的可序列化语义lookup对外的API方法调用时传入的第二个参数falselookupAny对外的API方法调用时传入的第二个参数为true——实际上第二个参数若为false就必须检查被解析类是否具有“可序列化”的语义这里的可序列化语义仅仅检查传入类是否实现了Serializable接口
            if (!(all || Serializable.class.isAssignableFrom(cl))) {
                return null;
            }
  2. 使用processQueue方法清空Caches类中的缓存哈希表localDescs,其缓存清除的哈希表键值是保存于一个ReferenceQueue队列localDescsQueue中,清除这个Caches类中的缓存哈希表时,需要根据ReferenceQueue中保存的键值来清除缓存中的项,从这一点可以知道Caches类中的四个成员是成对使用的,一对用于分析类,另外一对用于分析成员属性
            processQueue(Caches.localDescsQueue, Caches.localDescs);
  3. 通过传入的cl参数以及Caches中的localDescsQueue属性构造一个新的WeakClassKey对象key,这个引用key会在后边作为缓存哈希表的键值
    WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
  4. 利用上一步创建的WeakClassKey对象作为哈希表键值,尝试先从缓存Caches.localDescs的哈希表中获取一个存在的引用ref
            Reference ref = Caches.localDescs.get(key);
  5. 创建一个新的Object引用entry,赋值为null;若第四步获取的ref引用不为空,则直接将ref指向的对象【缓存中】赋给entry
            Object entry = null;
            if (ref != null) {
                entry = ref.get();
            }
  6. 创建一个新的EntryFuture引用future,如果entrynull的时候则配合Caches中的缓存localDescs初始化entry
            EntryFuture future = null;
            if (entry == null) {
                EntryFuture newEntry = new EntryFuture();
                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;
                }
            }
  7. 如果从缓存中拿到的entry类型本身就是ObjectStreamClass则直接返回这个entry
            if (entry instanceof ObjectStreamClass) {  // check common case first
                return (ObjectStreamClass) entry;
            }
  8. 如果从缓存中拿到的entry类型为EntryFuture,则从EntryFuture中去获取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();
                }
            }
  9. 到目前为止拿到的entry还是一个null的时候就直接使用当前的cl参数构造一个新的ObjectStreamClass,构造完成过后将entry放入localDescs缓存中;
            if (entry == null) {
                try {
                    entry = new ObjectStreamClass(cl);
                } catch (Throwable th) {
                    entry = th;
                }
                if (future.set(entry)) {
                    Caches.localDescs.put(key, new SoftReference(entry));
                } else {
                    // nested lookup call already set future
                    entry = future.get();
                }
            } 
               
  10. 最终判断entry的类型执行异常处理或者返回;
            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);
            }
  11.   15)构造函数
      ObjectStreamClass到这里其实代码执行流程的内容都还没怎么解析,接下来看看该类的构造函数。该类有两个构造函数,先看看它的构造函数定义:
        private ObjectStreamClass(final Class cl) {
            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;
    
            if (serializable) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        if (isEnum) {
                            suid = Long.valueOf(0);
                            fields = NO_FIELDS;
                            return null;
                        }
                        if (cl.isArray()) {
                            fields = NO_FIELDS;
                            return null;
                        }
    
                        suid = getDeclaredSUID(cl);
                        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;
            }
    
            try {
                fieldRefl = getReflector(fields, this);
            } catch (InvalidClassException ex) {
                // field mismatches impossible when matching local fields vs. self
                throw new InternalError();
            }
    
            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");
                }
            }
        }
        ObjectStreamClass() {
        }
      从上边的代码可以知道,第二个构造函数没有必要解析,ObjectStreamClass无参构造函数用于构造一个空白的类描述信息,若创建了这样的一个实例,则需要在之后调用initProxyinitNonProxyreadNonProxy方法来执行类的元数据初始化操作。那么这里看看第一个构造函数究竟做了些什么事:
    1. 设置成员属性的基本信息,传入的是一个Class类型的参数cl,可以通过Java反射获得基本成员属性的信息:
      cl——直接将该引用指向传入的类;
      name——获取传入类的类名
      isProxy——调用java.lang.reflect.ProxyisProxyClass方法判断传入类是否是一个Java中的动态代理类
      isEnum——判断当前类是否是一个Enum枚举的类;
      serializable——判断当前类是否实现了Serializable接口;
      externalizable——判断当前类是否实现了Externalizable接口;
    2. 设置localDescsuperDesc两个成员属性:
      superDesc——通过getSuperclass方法获取当前类的父类,然后通过lookup方法获取父类元数据信息,生成一个ObjectStreamClass实例,第二个参数为false表示要求检测父类是否实现了Serializable接口的;
      localDesc——直接将当前ObjectStreamClass引用this赋值给localDesc引用;
    3. 然后需要判断当前解析类是否支持可序列化【实现Serializable接口,这个时候不需要通过Serializable类来判断被解析类的“可序列化”的语义,直接检查成员属性serializable即可——注意这里会有一个包含关系,如果一个类是实现了Externalizable接口的,那么这个类一定实现Serializable接口的,前文提到过Serializable接口是Externalizable的父接口;
      如果当前解析类没有实现Serializable接口,则设置成员属性suidfields的值:
      suid——0L
      fields——NO_FIELDS
    4. 如果当前解析类实现了Serializable接口,代码会先调用Java语言中的访问控制器来判断代码是否拥有执行权限
    5. 通过Java的访问控制器过后,则同样需要设置可序列化的情况下成员属性suidfields的值,先看看设置这两个成员属性设置的过程:
                          if (isEnum) {
                              suid = Long.valueOf(0);
                              fields = NO_FIELDS;
                              return null;
                          }
                          if (cl.isArray()) {
                              fields = NO_FIELDS;
                              return null;
                          }
      
                          suid = getDeclaredSUID(cl);
                          try {
                              fields = getSerialFields(cl);
                              computeFieldOffsets();
                          } catch (InvalidClassException e) {
                              serializeEx = deserializeEx =
                                  new ExceptionInfo(e.classname, e.getMessage());
                              fields = NO_FIELDS;
                          }
      从上述代码可以看到,这段代码的设置如下:
      如果被解析类是枚举类型——suid的值为0Lfields的值为NO_FIELDS
      如果被解析类是数组类型——fields的值为NO_FIELDSsuid的值会继续运算;
      suid的值的计算——getDeclaredSUID方法运算,前文分析过getDeclaredSUID方法的代码细节,这里不重复;
      fields的值的计算——getSerialFields方法运算,前文分析过getSerialFields方法的代码细节,这里不重复;
      *:这个地方有个小细节,在代码中调用了computeFieldOffsets方法,这个方法调用过后,两个成员属性primDataSize和numObjFields的值也就有了【不针对枚举类型和数组类型】;
    6. 在设置了suidfields两个成员属性的值过后,则需要设置下边几个成员属性:
      cons、writeObjectMethod、readObjectMethod、readObjectNoDataMethod、hasWriteObjectData、writeReplaceMethod、readResolveMethod

                          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;
      相信不需要解释上边代码的含义了。代码执行到这里,大部分成员属性的构造初始化过程都完成了;
    7. 接下来就初始化fields以及fieldRefl两个成员属性,通过调用getReflector方法来计算这两个属性的值,回顾一下上边的getReflector方法传入的参数fields是传的引用,所以在执行getReflector方法后传入的fields的值就被初始化了,而且该方法的返回对象的引用会传给fieldRefl——getReflector方法执行过后具有“二义性”,它不是单纯地返回了一个FieldReflector,在执行过程中还修改了fields引用的对象的值;
    8. 最后看看剩余的几个和异常信息相关的成员属性的初始化:
              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");
                  }
              }
      前文中虽然讲过几个异常信息类,这里先看看第5步解析的代码中的catch块的内容:
                              serializeEx = deserializeEx =
                                  new ExceptionInfo(e.classname, e.getMessage());
                              fields = NO_FIELDS;
      从上边这段代码中可以看到serializeExdeserializeEx会在获取被解析类的可序列化字段元数据过程中因为出现异常而设置;其实isEnum为空的语句是一句“防御式”的编程,在前边调用过程如果isEnum判断为真则serializeExdeserializeEx都会为null,而这里再作判断的情况可能就属于十分稀有的情况了;
      16)初始化方法
      到这里ObjectStreamClass中就只剩下4个成员函数没有解析了:initProxy、initNonProxy、readNonProxy、writeNonProxy,这四个方法属于ObjectStreamClass中的核心方法,所以这四个方法会在接下来重点解析。本章节先看看初始化解析方法——initProxyinitNonProxy成员函数,initProxy负责解析动态代理类initNonProxy负责解析动态代理类,这两个方法会用于初始化当前ObjectStreamClass元数据实例。
      initProxy成员函数:
        void initProxy(Class cl,
                       ClassNotFoundException resolveEx,
                       ObjectStreamClass superDesc)
            throws InvalidClassException
        {
            this.cl = cl;
            this.resolveEx = resolveEx;
            this.superDesc = superDesc;
            isProxy = true;
            serializable = true;
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
    
            if (cl != null) {
                localDesc = lookup(cl, true);
                if (!localDesc.isProxy) {
                    throw new InvalidClassException(
                        "cannot bind proxy descriptor to a non-proxy class");
                }
                name = localDesc.name;
                externalizable = localDesc.externalizable;
                cons = localDesc.cons;
                writeReplaceMethod = localDesc.writeReplaceMethod;
                readResolveMethod = localDesc.readResolveMethod;
                deserializeEx = localDesc.deserializeEx;
            }
            fieldRefl = getReflector(fields, localDesc);
        }
      initProxy主要用于解析动态代理类初始化ObjectStreamClass实例,这个成员函数有三个参数,先看看这三个参数的含义:
    • cl——java.lang.Class
      该参数表示需要被解析类,该方法解析的元数据信息就是从这个类中提取的;
    • resolveEx——java.lang.ClassNotFoundException
      该参数用于处理启用”resolve”功能时的异常信息
    • superDesc——java.io.ObjectStreamClass
      该参数表示当前类对应的父类的元数据信息;
      看看这个函数中的细节代码流程,看看究竟initProxy初始化的时候做了些什么。
    1. 看看设置成员函数的过程:
      cl——将传入的Class类型的参数赋给成员属性cl
      resolveEx——将传入的ClassNotFoundException类型的参数赋给成员属性resolveExt
      superDesc——将传入的ObjectStreamClass类型的参数赋给成员属性superDesc
      isProxy——因为这个成员函数的作用就是解析动态代理类,所以isProxy设置为true
      serializable——标识当前解析类是可序列化的,设置值为true
      suid——因为解析类是动态代理类,所以suid0L
      fields——同上述理由,设置fields的值为NO_FIELDS
    2. 如果成员函数cl不为null,则执行额外逻辑:
      localDesc——调用lookup方法解析传入类,获取该类的元数据信息,并将该元数据引用赋给localDesclocalDesc赋值过后,判断解析类是否为一个动态代理类,如果不是动态代理类则抛出InvalidClassException异常;
      下边的几个成员属性直接从localDesc中读取:
      name、externalizable、cons、writeReplaceMethod、readResolveMethod、deserializable
    3. 最后设置成员属性fieldRefls,直接调用getReflector方法获得;
      initNonProxy成员函数:
        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 ExceptionInfo(
                            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();
        }
      initNonProxy成员函数用于解析非动态代理类初始化ObjectStreamClass实例,这个成员函数initProxy成员函数多了一个参数:
    • model——java.io.ObjectStreamClass
      该参数表示需要传入的元数据信息,调用该成员函数的时候会利用传入的元数据信息为当前实例赋值;
      看看这个成员函数的细节代码流程,并且对比initProxy函数看看二者有什么不同:
    1. 成员属性cl、resolveEx、superDescinitProxy使用相同的方式赋值;
    2. 成员属性name、isEnum、serializable、externalizable、hasBlockExternalData、hasWriteObjectData、fields、primDataSize、numObjects直接从传入参数model中取得;
    3. 成员属性suidisProxy的设置稍有不同,suid通过调用modelgetSerialVersionUID方法获得,isProxy直接设置成false——因为initNonProxy成员函数的目的是解析动态代理类,所以直接为isProxy赋值;
    4. 当成员cl不为null时,initNonProxy的执行流程和initProxy差不多,唯一不同的是多了许多异常信息,看看抛出异常的情况:
      ——如果被解析类为动态代理类则抛出异常;
      ——如果model中的isEnum【第二步已经赋值给isEnum了】localDesc中的isEnum不相等则抛出异常;
      ——如果model中的serializablelocalDesc中的serializable相等、并且cl不是数组、并且modelsuid的值和localDescsuid的值不相等时,则抛出异常【前边三个条件同时满足才行】
      ——如果model中的namelocalDesc中的name不相等时则抛出异常;
      ——如果被解析类不是枚举类型时抛异常的情况分为下边两种:
        (1) 如果modelserializablelocalDesc中的serializable相等,但externalizable不相等则抛出异常;
        (2) 如果modellocalDesc描述的类任何一个类没有序列化语义则抛出异常【未实现Serializable或者Externalizable接口】
    5. fieldRefl成员属性的赋值和initProxy一样;
    6. initNonProxy成员属性多了下边这行代码,至于目的可见注释部分内容:
              // reassign to matched fields so as to reflect local unshared settings
              fields = fieldRefl.getFields();
      17)序列化 & 反序列化
      ObjectStreamClass类中最后需要解析的成员函数是writeNonProxyreadNonProxy,这两个成员函数负责序列化和反序列化操作,在ObjectOutputStream中会调用这两个方法针对对象所属类的元数据执行序列化或反序列化操作【*:这个类中只包含了非动态代理类的序列化和反序列化方法,所以名称后缀为NonProxy】。这两个函数的参数一个是ObjectOutputStream类型,另外一个参数是ObjectInputStream类型,这两个类后边会解析,这里不详讲。
      writeNonProxy序列化函数:
        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());
                }
            }
        }
      看看这个方法的代码流程:
    1. 先调用writeUTF方法写入类名到字节流,这里的类名为带了包名的类全名:
              out.writeUTF(name);
    2. 然后调用writeLong方法写入suid的值到字节流,也就是前边解析二进制序列时serialVersionUID的值;
              out.writeLong(getSerialVersionUID());
    3. 写入标记到字节流,这里的标记为前文提到的五个SC_*标记,注意这个标记只占一个字节的数据,flags的类型为byte,至于标记的运算细节很简单,读者可仔细阅读下边的代码:
              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);
    4. 写入当前类中成员属性的数量信息到字节流:
              out.writeShort(fields.length);
    5. 最后写入每一个字段的信息,字段信息包含三部分内容:类型代码、字段名、字段类型字符串【该信息不适用于基础类型的数据】
              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());
                  }
              }
      readNonProxy反序列化函数:
        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();
        }
      看看这个方法的代码流程:
    1. 先从字节流中读取类名信息:
      name = in.readUTF();
    2. 其次从字节流中读取suid的信息,即类的serialVersionUID信息:
              suid = Long.valueOf(in.readLong());
    3. 从字节流中读取SC_*标记信息,通过该标记信息设置对应的成员属性
              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);
              }
    4. 从字节流中读取成员属性的数量信息:
              int numFields = in.readShort();
              if (isEnum && numFields != 0) {
                  throw new InvalidClassException(name,
                      "enum descriptor has non-zero field count: " + numFields);
              }
    5. 最后从字节流中读取每一个字段的信息,字段信息包括:类型代码、字段名、字段类型字符串【非基础类型数据】
              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);
                  }
              }
      至于readNonProxy方法和writeNonProxy方法到异同,请读者结合上边解析过的代码仔细理解。
      到这里,ObjectSreamClass的内容就全部解析完成了,阅读了这么多内容读者是否还是很困惑呢?前文内容庞大,所以在此先总结一下。ObjectStreamClassObjectStreamField两个类充当了解析过程中容器的作用,ObjectStreamClass实例表示被解析类的元数据信息,ObjectStreamField实例表示成员属性的元数据信息,所有上文提到的代码中,只有ObjectStreamClass中有两个真正和序列化以及反序列化过程相关的方法,就是writeNonProxyreadNonProxyObjectStreamClass以及ObjectStreamField使用得最多的概念就是“封装”,不仅仅是这两个类,实际上ObjectStreamClass的内部类也封装了很多信息,至于这两个类中错综复杂的结构只是为了提供统一的方式来解析元数据。整体的概念需要注意的点如下:
    • 签名信息
      ——包括:成员属性签名、成员函数签名、构造函数签名,结合代码中的signature去理解;
    • 关于类型
      ——区分:类型、类型代码、类型字符串的概念;
    • 五个SC标记
      ——SC_WRITE_METHOD、SC_BLOCKDATA、SC_SERIALIZABLE、SC_EXTERNALIZABLE、SC_ENUM,这五个标记的概念在源代码中多次涉及到;
    • 缓存
      ——理解ObjectStreamClass中的Caches类,通过本文的截图理解缓存的数据结构;
    • 两种Key:WeakClassKey & FieldReflectorKey
      ——理解两个内部类WeakClassKeyFieldRefectorKey的用法,这两个Key一般作为哈希表缓存的键值使用;
    • ClassDataSlot
      ——理解ClassDataSlot在源代码中的概念;
    • SUID
      ——结合前文JVM的序列化规范中提及的SUID理解源代码中针对SUID的计算方法;
    • FieldReflector
      ——理解源代码中内部类FieldReflector的用法,看看它是如何去分析类中的各个字段的;
    • serialPersistentFields
      ——这是一个Java类处理可序列化成员属性的另外一种方式,理解ObjectStreamClass中如何处理该字段;
    • 字段数组
      ——源代码中多次获取了ObjectStreamField[]的字段元数据数组,理解源代码中和这点相关的所有方法;
    • 特殊方法
      ——可序列化的类中包含了五个特殊方法,看看这五个特殊方法在源代码中如何处理:writeObject、readObject、readObjectNoData、writeReplace、readResolve
    • 异常管理
      ——管理系统中的所有异常信息,理解如何在源代码中处理异常信息;
    • lookup
      ——理解ObjectStreamClass中的lookup方法,对比这两个方法和构造函数如何设置成员属性的相关信息;
    • Java语言基础
      ——若要理解上述源代码需要准备的Java知识:反射、动态代理类、安全检测、数据类型、基础算法等;
      列举出这些代码相关的内容,希望读者以上述内容作为索引去理解源代码的内容,下一个章节开始我们就来看看序列化和反序列化中的另外两个核心类。

    你可能感兴趣的:(Java系列教程)