java反序列化demo

1、代码

Info.java //创建一个info类,该类引用接口Serializable,该类可以序列化和反序列化
引用了Serializable接口时,会在类内寻找writeObject(readObject)方法,如果重写了该方法则使用重写后的, 如果未重写,则使用默认的
关键函数resolveClass(),通过java反射机制,获取重写后的readObject方法,这里是因为原本的java有readobject方法,必须通过java反射机制在执行时重写readobject
执行流程如下

ObjectInputSteram.readObject()
	readObject0()
		readOrdinaryObject()
			desc = readClassDesc(false)
				descriptor = readNonProxyDesc(unshared)
					readDesc = readClassDescriptor()
					cl = resolveClass(readDesc)
					filterCheck(cl, -1)
					desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false))
					各种初始化、检查 suid 等
					return desc
				return descriptor
			obj = desc.isInstantiable() ? desc.newInstance() : null
			readSerialData(obj, desc)
				slotDesc.invokeReadObject(obj, this)
					readObjectMethod.invoke(obj, new Object[]{ in })	

测试代码

接口类Info

    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    public class Info implements Serializable { //引用Serializable类,一个标志位,标志该类可以序列化和反序列化
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    private void readObject(ObjectInputStream s) throws IOException{ //重写了Serializable的readobject方法,实现反序列化时的输出,注意这里的类修饰方法必须是private
        System.out.println("test");
    }
}

主类Main
实现序列化和反序列化函数,并且调用

    import java.io.*;

    public class Main {
    public static void main(String[] args) throws Exception {
        Info i =  new Info(); //实例化类Info
        i.setName("alpenliebe");
        byte[] bytes = serialize(i); //序列化类Info
        Info i2 = (Info)unserialize(bytes); //反序列化类Info
        System.out.println(i2.getName());
    }

    public static byte[] serialize(Object obj) throws IOException { //序列化函数,读入一个object返回一个字节流

        ByteArrayOutputStream baos = new ByteArrayOutputStream(); //申请一块数组缓冲区
        ObjectOutputStream oos = new ObjectOutputStream(baos); //实例化对象到字节的对象,需要一块数组的缓冲区
        oos.writeObject(obj); //对象到字节的对象调用写入对象,把对象写入到缓冲区中
        return baos.toByteArray(); //把缓冲区的内容序列化成字节流
    }

    public static Object unserialize(byte[] bytes) throws Exception{ //反序列化函数,读入一个字节流,返回一个object
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); //申请一个输入字节流空间,并将字节流传入到空间内
        return ois.readObject(); //把缓冲区的内容反序列化成对象
    }
}

2、调试分析

在readObject()处打上断点开始分析,跟进readObject()
首先执行一个enableOverride判断,只要是由带参数的 constructor 建立的 ObjectInputStream 实力,这个参数的值预设就是 false
当constructor 沒有参数時,才会将 enableOverride设成 true:
这里没什么用,接着下来执行readObject0()
java反序列化demo

跟入readObject0()
BlockDataInputStream是ObjectInputStream底层的资料读取类,用来完成对序列化Stream的读取
其分分为两种读取模式: Default mode 和 Block mode
从代码里可以看到,如果是 Block mode,会检查当前 block 是否有剩余的 bytes,都没有就转 Default mode
接着 tc = bin.peekByte()会去呼叫 PeekInputStream.peek()
这个 PeekInputStream 类背后是继承 InputStream 类,最后呼叫的是 InputStream.read()
所以其实 tc 就是从序列化 Stream 中读一个 Byte 出來
以我门前面的测试 class那个例子來说,可以用 SerializationDumper 这个工具来查看反序列化流具体执行过程
根据其结果,可以知道 tc 会走到 TC_OBJECT

        private Object readObject0(Class<?> type, boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode(); //这里由constructor做初始化,序列号stream的读取器
        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:
                    // check the type of the existing object
                    return type.cast(readHandle(unshared));

                case TC_CLASS:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast a class to java.lang.String");
                    }
                    return readClassDesc(unshared);

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

                case TC_ARRAY:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an array to java.lang.String");
                    }
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an enum to java.lang.String");
                    }
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an exception to java.lang.String");
                    }
                    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);
        }
    }

常量TC_OBJECT 对应的整数是 0x73
(参考https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/aa318070b27849f1fe00d14684b2a40f7b29bf79/jdk/src/share/classes/java/io/ObjectStreamConstants.java#L72)
代表读进来的是个 object

case TC_OBJECT:
	if (type == String.class) {
		throw new ClassCastException("Cannot cast an object to java.lang.String");
  }
  return checkResolve(readOrdinaryObject(unshared));

继续跟进 checkResolve(readOrdinaryObject(unshared)) 中的readOrdinaryObject(unshared)
开头执行了个判断,如果不是TC_OBJECT传入的,直接返回一个错误,接下来呼叫readClassDesc

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

        ObjectStreamClass desc = readClassDesc(false);
        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;
        } 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);
            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;
    }

跟进readClassDesc()
这个函数的功能就跟方法名描述的一样,会尝试从序列化 Stream 中,构造出 class descriptor
以这个栗子来说,第一个 Byte 读到的会是 TC_CLASSDESC (0x72),代表 Class Descriptor,就算一种用来描述类的结构,包含类名字、成员类型等描述
所以接下來会呼叫 descriptor = readNonProxyDesc(unshared) 来读出这个 class descriptor

    private ObjectStreamClass readClassDesc(boolean unshared)
        throws IOException
    {
        byte tc = bin.peekByte();
        ObjectStreamClass descriptor;
        switch (tc) {
            case TC_NULL:
                descriptor = (ObjectStreamClass) readNull();
                break;
            case TC_REFERENCE:
                descriptor = (ObjectStreamClass) readHandle(unshared);
                break;
            case TC_PROXYCLASSDESC:
                descriptor = readProxyDesc(unshared);
                break;
            case TC_CLASSDESC:
                descriptor = readNonProxyDesc(unshared);
                break;
            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
        if (descriptor != null) {
            validateDescriptor(descriptor);
        }
        return descriptor;
    }

跟入readNonProxyDesc(unshared)
这里会先初始化一个ObjectStreamClass变量desc,他代表的就是序列化 class descriptor
接着后面呼叫 readClassDescriptor()方法

     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));
        } finally {
            depth--;
        }

        handles.finish(descHandle);
        passHandle = descHandle;

        return desc;
    }

跟进readClassDescriptor()方法
内部一样会去初始化一个 ObjectStreamClass 变量
接着呼叫readNonProxy()

    protected ObjectStreamClass readClassDescriptor()
        throws IOException, ClassNotFoundException
    {
        ObjectStreamClass desc = new ObjectStreamClass();
        desc.readNonProxy(this);
        return desc;
    }

跟进readNonProxy()
其中readUTF读取上面说的 class descriptor表示的名字
suid = Long.valueOf(in.readLong())就是读出serialVersionUID,版本标识位一般没什么用,如果不一致会抛出异常
执行完毕,上层函数readClassDescriptor()会把结果回传

   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();
    }

刚刚初始化完的 class descriptor readDesc 执行到 resolveClass()
而 resolveClass() 做的事情很简单,通过反射,取得并回传当前 descriptor 描述的类,也就是我们代码中的Main这个类

    protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

接着执行到filterCheck(cl, -1)中,这里的cl就是刚才resolveClass()回传的结果
这里可以看到 serialFilter 是在 ObjectInputStream 初始化时取得的
当serialFilter 存在时,filtercheck 会去做检查、过滤,如果没通过就直接拋出异常
serialFilter = ObjectInputFilter.Config.getSerialFilter();
接着执行到readNonProxyDesc类中的desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false))

    private void filterCheck(Class<?> clazz, int arrayLength)
            throws InvalidClassException {
        if (serialFilter != null) {
            RuntimeException ex = null;
            ObjectInputFilter.Status status;
            // Info about the stream is not available if overridden by subclass, return 0
            long bytesRead = (bin == null) ? 0 : bin.getBytesRead();
            try {
                status = serialFilter.checkInput(new FilterValues(clazz, arrayLength,
                        totalObjectRefs, depth, bytesRead));
            } catch (RuntimeException e) {
                // Preventive interception of an exception to log
                status = ObjectInputFilter.Status.REJECTED;
                ex = e;
            }
            if (status == null  ||
                    status == ObjectInputFilter.Status.REJECTED) {
                // Debug logging of filter checks that fail
                if (Logging.infoLogger != null) {
                    Logging.infoLogger.info(
                            "ObjectInputFilter {0}: {1}, array length: {2}, nRefs: {3}, depth: {4}, bytes: {5}, ex: {6}",
                            status, clazz, arrayLength, totalObjectRefs, depth, bytesRead,
                            Objects.toString(ex, "n/a"));
                }
                InvalidClassException ice = new InvalidClassException("filter status: " + status);
                ice.initCause(ex);
                throw ice;
            } else {
                // Trace logging for those that succeed
                if (Logging.traceLogger != null) {
                    Logging.traceLogger.finer(
                            "ObjectInputFilter {0}: {1}, array length: {2}, nRefs: {3}, depth: {4}, bytes: {5}, ex: {6}",
                            status, clazz, arrayLength, totalObjectRefs, depth, bytesRead,
                            Objects.toString(ex, "n/a"));
                }
            }
        }
    }

跟进initNonProxy()
这个方法做了很多初始化操作
包括前面说的 suid 检查、计算等,在这个方法中都会处理
参数 model 是刚刚从序列化 Stream 中,读出来的 readDesc,而目前 initNonProxy 这个方法是由前面建立的 desc 呼叫的
这个方法会使用 readDesc (反序列化还原出來的) 属性来初始化 desc,所以必須先检查 readDesc 正确性
为了检查 readDesc 正确性,会判断跟本地直接 new 出来的localDesc 的 suid, class name 等内容是否相同,如果不同抛出异常
其中 localDesc = lookup(cl, true) 是根据 class,返回对应的 class descriptor:

上一篇:ElasticSearch7.6.2--SpringBoot


下一篇:thinkphp5 链式操作comment用法