【JDK源码分析】02-对象反序列化ObjectInputStream

上一篇文章《对象序列化ObjectOutputStream》描述了对象的序列化,下面我们分析一下对象的反序列化就知道了为啥枚举反序列化后还是单例,而普通类不可以。

public ObjectInputStream(InputStream in) throws IOException {
    verifySubclass();
    bin = new BlockDataInputStream(in);
    handles = new HandleTable(10);
    vlist = new ValidationList();
    serialFilter = ObjectInputFilter.Config.getSerialFilter();
    enableOverride = false;
    readStreamHeader();
    bin.setBlockDataMode(true);
}
 
protected void readStreamHeader()
    throws IOException, StreamCorruptedException
{
    short s0 = bin.readShort();
    short s1 = bin.readShort();
    if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
        throw new StreamCorruptedException(
            String.format("invalid stream header: %04X%04X", s0, s1));
    }
}

可以看到ObjectInputStream构造方法中调用readStreamHeader()方法首先读出maigc和版本号,然后调用readObject()方法,其中又调用了readObject0()方法,此方法的返回值就是反序列化后的对象了。

private Object readObject0(boolean unshared) throws IOException {
    boolean oldMode = bin.getBlockDataMode();
    if (oldMode) {
        int remain = bin.currentBlockRemaining();
        if (remain > 0) {
            throw new OptionalDataException(remain);
        } else if (defaultDataEnd) {
            /*
             * Fix for 4360508: stream is currently at the end of a field
             * value block written via default serialization; since there
             * is no terminating TC_ENDBLOCKDATA tag, simulate
             * end-of-custom-data behavior explicitly.
             */
            throw new OptionalDataException(true);
        }
        bin.setBlockDataMode(false);
    }

    byte tc;
    while ((tc = bin.peekByte()) == TC_RESET) {//继续读取下一个字节,这个字节表示序列化对象的类型
        bin.readByte();
        handleReset();
    }

    depth++;
    totalObjectRefs++;
    try {
        switch (tc) {
            case TC_NULL:
                return readNull();

            case TC_REFERENCE:
                return readHandle(unshared);

            case TC_CLASS:
                return readClass(unshared);

            case TC_CLASSDESC:
            case TC_PROXYCLASSDESC:
                return readClassDesc(unshared);

            case TC_STRING:
            case TC_LONGSTRING:
                return checkResolve(readString(unshared));

            case TC_ARRAY:
                return checkResolve(readArray(unshared));

            case TC_ENUM://1⃣️枚举
                return checkResolve(readEnum(unshared));

            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));

            case TC_EXCEPTION:
                IOException ex = readFatalException();
                throw new WriteAbortedException("writing aborted", ex);

            case TC_BLOCKDATA:
            case TC_BLOCKDATALONG:
                if (oldMode) {
                    bin.setBlockDataMode(true);
                    bin.peek();             // force header read
                    throw new OptionalDataException(
                        bin.currentBlockRemaining());
                } else {
                    throw new StreamCorruptedException(
                        "unexpected block data");
                }

            case TC_ENDBLOCKDATA:
                if (oldMode) {
                    throw new OptionalDataException(true);
                } else {
                    throw new StreamCorruptedException(
                        "unexpected end of block data");
                }

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}

我们首先看一下对枚举的反序列化过程

case TC_ENUM://1⃣️枚举
                return checkResolve(readEnum(unshared));
private Enum<?> readEnum(boolean unshared) throws IOException {
    if (bin.readByte() != TC_ENUM) {
        throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);//从输入流读取的字节流转换为枚举对应的类描述信息,这个对象包含了该类所有的信息
    if (!desc.isEnum()) {
        throw new InvalidClassException("non-enum class: " + desc);
    }

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(enumHandle, resolveEx);
    }

    String name = readString(false);//读取枚举类的全限定名
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum<?> en = Enum.valueOf((Class)cl, name);//下面重点分析一下这个方法
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

上面代码核心代码就三行

  1. ObjectStreamClass desc = readClassDesc(false);//从输入流读取的字节流转换为枚举对应的类描述信息,这个对象包含了该类所有的信息
  2. String name = readString(false);//读取枚举类的全限定名
  3. Enum<?> en = Enum.valueOf((Class)cl, name);//下面重点分析一下这个方法

下面我们分析一下为啥Enum.valueOf((Class)cl, name)可以将类名转换成枚举对象并且是单例的,这里还是以上篇文章出现的Gender枚举举例,这是Gender的源代码

public enum Gender {
    MAN,WOMAN;
}

我们用jad反编译一下Gender.class文件

package com.jdk.source.objectstream;

public final class Gender extends Enum

{

    public static Gender[] values()

    {

        return (Gender[])$VALUES.clone();

    }

    public static Gender valueOf(String name)

    {

        return (Gender)Enum.valueOf(com/jdk/source/objectstream/Gender, name);

    }

    private Gender(String s, int i)

    {

        super(s, i);

    }

    public static final Gender MAN;

    public static final Gender WOMAN;

    private static final Gender $VALUES[];

    static 

    {

        MAN = new Gender("MAN", 0);

        WOMAN = new Gender("WOMAN", 1);

        $VALUES = (new Gender[] {

            MAN, WOMAN

        });

    }

}

我们通过反编译的结果可以看到在静态代码块中编译器为Gender自动实例化了两个static final Gender对象MAN和WOMAN和一个values ()方法返回一个包含MAN和WOMAN的数组。让我们回头再看一下Enum.valueOf()方法

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

调用了Class对象的enumConstantDirectory()方法返回一个Map,key是枚举名称value是枚举对象。

Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
        T[] universe = getEnumConstantsShared();//内部就是调用上面反编译枚举类中的values方法
        if (universe == null)
            throw new IllegalArgumentException(
                getName() + " is not an enum type");
        Map<String, T> m = new HashMap<>(2 * universe.length);
        for (T constant : universe)
            m.put(((Enum<?>)constant).name(), constant);新建一个Map存放枚举对象
        enumConstantDirectory = m;
    }
    return enumConstantDirectory;
}
T[] getEnumConstantsShared() {
    if (enumConstants == null) {
        if (!isEnum()) return null;
        try {
            final Method values = getMethod("values");//相当于通过反射调用Gender的静态values方法
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                            values.setAccessible(true);
                            return null;
                        }
                    });
            @SuppressWarnings("unchecked")
            T[] temporaryConstants = (T[])values.invoke(null);
            enumConstants = temporaryConstants;
        }
        // These can happen when users concoct enum-like classes
        // that don't comply with the enum spec.
        catch (InvocationTargetException | NoSuchMethodException |
               IllegalAccessException ex) { return null; }
    }
    return enumConstants;
}

到这我们就明白了,总结一下,编译器会为枚举类通过静态代码块实例化在枚举中定义的枚举值(MAN和WOMAN),因为是静态变量所有在内存中只存在一份,反序列化的时候通过Enum.valueOf()方法得到一个key为枚举名称value为静态的Gender类的对象,通过map.get(枚举名称)得到的就是Gender类其中的一个静态成员变量。因为枚举的反序列化得到的对象依然引用的原Gender类静态代码块创建的对象并没有新创见一个对象,因此还是单例的。

我们再看看普通类的反序列化

case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));
private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if (bin.readByte() != TC_OBJECT) {
        throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);//#1 从流中读取类的描述信息,会做一些校验如序列号
    desc.checkDeserialize();

    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }

    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;//#2 通过无参构造函数直接创建对象,这也就是说为啥反序列化类不是单例了
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }

    passHandle = handles.assign(unshared ? unsharedMarker : obj);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(passHandle, resolveEx);
    }

    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc);
    }

    handles.finish(passHandle);

    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);// #3 一个钩子,让我们可以自定义反序列化后的对象
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }

    return obj;
}
上面代码比较重要的有三点,看注释#1,#2,#3。然后我们逐个分析一下。

#1:readClassDesc()中又调用了readNonProxyDesc()方法,我们来看一下

private ObjectStreamClass readNonProxyDesc(boolean unshared)
    throws IOException
{
    if (bin.readByte() != TC_CLASSDESC) {
        throw new InternalError();
    }

    ObjectStreamClass desc = new ObjectStreamClass();
    int descHandle = handles.assign(unshared ? unsharedMarker : desc);
    passHandle = NULL_HANDLE;

    ObjectStreamClass readDesc = null;
    try {
        readDesc = readClassDescriptor();// 从流中解析出类的描述信息
    } catch (ClassNotFoundException ex) {
        throw (IOException) new InvalidClassException(
            "failed to read class descriptor").initCause(ex);
    }

    Class<?> cl = null;
    ClassNotFoundException resolveEx = null;
    bin.setBlockDataMode(true);
    final boolean checksRequired = isCustomSubclass();
    try {
        if ((cl = resolveClass(readDesc)) == null) {
            resolveEx = new ClassNotFoundException("null class");
        } else if (checksRequired) {
            ReflectUtil.checkPackageAccess(cl);
        }
    } catch (ClassNotFoundException ex) {
        resolveEx = ex;
    }

    // Call filterCheck on the class before reading anything else
    filterCheck(cl, -1);

    skipCustomData();

    try {
        totalObjectRefs++;
        depth++;
        desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));// 将解析的ObjectStreamClass对象与内存中也有的对象进行一个jiao y,
    } finally {                                                             包括serialVersionUIO,是否为枚举,类名等,校验通过将字段描述信息赋值给desc
        depth--;                                                                   
} handles.finish(descHandle) ; passHandle = descHandle ; return desc ;}

当我们序列化类的版本号与当前内存中类对象版本号不一致就会抛InvalidClassException异常详细请看一下initNonProxy()方法。

#2:如果存在cons(关系最近的没有实现序列化接口父类的无参构造方法),则直接cons.newInstance()创建一个新对象

obj = desc.isInstantiable() ? desc.newInstance() : null;

boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}

后面readSerialData(obj,desc);就是将序列化的字段值赋值给obj的过程。

#3:类描述对象非静态 Object readResolve()方法时,会调用此方法将返回结果作为最终的反序列化的结果,所以说防止破坏单例的方法就是在类中定义一个名为readResolve()的私有方法返回单例对象即可,我们改造一下Person。

public class Person extends Human implements Serializable {
    public String xyz = "lmn";
    public String name;
    public int age = 55;
    public int length = 66;
    public int width = 77;
    public static Person single=new Person("hero");

    public Person(String name) {
        super(name);
        this.name = name;
    }
    private Object readResolve() {
        return single;
    }
}

测试一下

private static void testSeriableEnum() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);
        oos.writeObject(Gender.MAN);
        Person p=Person.single;
        oos.writeObject(p);
        System.out.println(baos.toString());
        ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bais);
        Object readEnum = ois.readObject();
        System.out.println(readEnum==Gender.MAN);
        Object readObject = ois.readObject();
        System.out.println(readObject==p);
    }

结果抛出异常java.io.InvalidClassException: com.jdk.source.objectstream.Person; no valid constructor

我们将Human加上无参构造方法再试,结果如下



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值