zoukankan      html  css  js  c++  java
  • Java反序列化创建对象探析

    通过反序列化生成对象的过程主要由以下几个步骤:

    1、创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

    2、 通过对象输入流的readObject()方法读取对象。

    其中正是readObject方法返回了一个对象,这个对象就是根据序列化生成的文件而创建的对象,所以反序列化如何创建对象关键就在于readObject方法的实现,那就来探析一下它的实现,它的源码如下:

     public final Object readObject()
            throws IOException, ClassNotFoundException
        {
            if (enableOverride) {
                return readObjectOverride();
            }
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
                Object obj = readObject0(false);
                handles.markDependency(outerHandle, passHandle);
                ClassNotFoundException ex = handles.lookupException(passHandle);
                if (ex != null) {
                    throw ex;
                }
                if (depth == 0) {
                    vlist.doCallbacks();
                }
                return obj;
            } finally {
                passHandle = outerHandle;
                if (closed && depth == 0) {
                    clear();
                }
            }
        }

    这个方法会从对象输入流中读取一个对象,其中还包括这个对象的类名、类的签名、类的非静态非瞬时属性值、以及它的所有超类。

    该对象所引用的对象是传递式读取的,所以readObject方法可以重建对象的完整等效图,也就复刻一个和原来被序列化对象一样的对象。

    由于该方法返回值就是一个Object类型,所以我们重点看实际返回的obj这个对象是怎么创建,可以看出它是通过另一个方法创建,继续查看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:
                        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);
            }
        }

    这个方法是readObject方法的底层实现,由于它的返回值类型也是对象类型,所以我们重点看返回的实际对象是哪个,通过观察和debug发现,开关语句执行时下面这行代码,checkResolve方法接收的参数类型是对象类型,返回值类型也是对象类型。

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

    那我们来查看readOrdinaryObject对象:

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

    此处我们重点看的还应该是产生实际返回值的地方,也就是这块:

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

    desc.newInstance()方法原理是利用反射创建了一个对象,本质是调用非序列化父类的无参构造器。

    如果该类是Externalizable类型的,则调用它自身的访问权限是public无参构造方法。

    如果该类是Serializable类型的,则调用该类的第一个非Serializable类型的父类的无参构造方法。

    所以这就是为什么通过反序列化创建对象的时候,并不会执行被序列化对象的构造方法。

    对于实现Serializable接口的类,并不要求该类具有一个无参的构造方法, 因为在反序列化的过程中实际上是去其继承树上找到一个没有实现Serializable接口的父类(最终会找到Object),然后构造该类的对象,再逐层往下的去设置各个可以反序列化的属性(也就是没有被transient修饰的非静态属性)。

    我们通过一个具体的案例就可以看出来:

     
    import java.io.Serializable;
    
    public class Users implements Serializable {
        private String username;
        private int age;
        private String sex;
    
        public Users() {
            System.out.println("调用了构造方法");
        }
      /*
        get和set方法
      */

      …………
    }

    创建一个测试类:

    import java.io.*;
    
    public class TestUsers {
        public static void main(String[] args) {
    
            try {
                System.out.println("开始序列化。。。。。");
                Users users = new Users();
                users.setUsername("fym");
                users.setAge(23);
                users.setSex("nan");
    
                FileOutputStream fs = new FileOutputStream("users.ser");
                ObjectOutputStream os = new ObjectOutputStream(fs);
                os.writeObject(users);
                os.close();
                fs.close();
    
                System.out.println("开始反序列化。。。。。");
                FileInputStream fileInputStream = new FileInputStream("users.ser");
                ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
                Users o = (Users)objectInputStream.readObject();
                System.out.println(o);
                fileInputStream.close();
                objectInputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    }

    输出结果:

    开始序列化。。。。。
    调用了构造方法
    开始反序列化。。。。。
    Users{username='fym', age=23, sex='nan'}

    我们将其改造一下,让User类继承一个没有序列化的父类:

    class ParentsUser{
        public ParentsUser(){
            System.out.println("调用了父类构造方法");
        }
    }
    public class Users extends ParentsUser implements Serializable {
        ……
    }    

    再一次执行上面的测试类,输出结果如下:

    开始序列化。。。。。
    调用了父类构造方法
    调用了构造方法
    开始反序列化。。。。。
    调用了父类构造方法
    Users{username='fym', age=23, sex='nan'}

    发现,反序列化创建对象的时候,果然调用了离User类最近的没有序列化的超类的无参构造函数。

  • 相关阅读:
    js中(function(){…})()立即执行函数写法理解
    JS 立即执行的函数表达式(function)写法
    javascript中call,apply,bind的用法对比分析
    C++成员函数指针的应用
    typeid详解
    dynamic_cast
    C++标准转换运算符dynamic_cast
    继承的构造函数
    考虑写一个不抛出异常的swap函数
    布隆过滤器(转)
  • 原文地址:https://www.cnblogs.com/yxym2016/p/12920315.html
Copyright © 2011-2022 走看看