美文网首页
java序列化,深度解析

java序列化,深度解析

作者: javaCrazer | 来源:发表于2021-01-11 21:04 被阅读0次

    对象序列化是什么?

    java程序启动会启动一个相应的jvm进程,在程序运行期间,若新生成了对象,则会在jvm堆上分配空间(大多数情况),进行对象的表示,即对象的生命周期是短于jvm的,对象只能存在jvm进程运行时,但在某些情况下,我们希望将内存中的对象状态(即对象的实例数据)保存起来,保存的地方可能是文件,可能是数据库,然后在将来的某一个时间读取保存的"对象"(此时是二进制数据的对象),将其恢复成内存中的对象。java的序列化机制即提供了这样一种功能,可以将内存中的对象转化为字节,字节可用于持久化保存,也可以进行远程传输(例如可以一台windows机器通过序列化将对象传输到另一台远端的linux机器)。反序列化自然就是将字节转化为内存中的对象

    如何使用?

    若需要类支持序列化功能,只需实现java.io.Serializable 接口即可。该接口是一个标记接口,不含有任何方法。此处创建一个支持序列化功能的类Person,供后面讲解。

    package demo;
    import java.io.Serializable;
    public class Person implements Serializable {
        
        private static final long serialVersionUID = -7763700527072968555L;
        private String name;
        private Integer age;
        private double height;
    
        public Person(String name, Integer age,double height) {
            System.out.println("Person的有参构造器");
            this.name = name;
            this.age = age;
            this.height=height;
        }
    
        public Person() {
            System.out.println("Person无参构造器");
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public double getHeight() {
            return height;
        }
    
        public void setHeight(double height) {
            this.height = height;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", height=" + height +
                    '}';
        }
    }
    

    测试类将可序列化的类保存到文件中

    package demo;
    import java.io.*;
    class PersonTest {
        public static void main(String[] args) throws Exception {
            String filePath="C:\\test\\person.out";
            Person person = new Person("zhangsan", 21,1.80);
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化保存到文件中
            outputStream.writeObject(person);
            outputStream.close();
            //将文件中的字节还原为内存中的对象(反序列化)
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person) inputStream.readObject();
            inputStream.close();
            System.out.println(readObject);
        }
    
    }
    

    测试结果为

    Person的有参构造器
    Person{name='zhangsan', age=21, height=1.8}
    

    从输出结果可以知道两点:1.Serializable实现类反序列化的时候不会调用类的构造器,会直接根据保存的字节序列还原成内存中的对象。 2.引用类型数据和基础类型数据均可以序列化

    默认的序列化机制

    若对象里面也引用了其他对象,则默认的序列化机制会递归序列化,直到所有的对象都被序列化。(被引用的对象也需要支持序列化,否则不能被序列化,会抛出NotSerializableException异常)。增加Address类,并在前面的Person类中增加Address类

    package demo;
    import java.io.Serializable;
    public class Address implements Serializable {
        
        private static final long serialVersionUID = 8670510229642968881L;
        private String province;
        private String city;
        private String county;
        
        public Address(String province, String city, String county) {
            this.province = province;
            this.city = city;
            this.county = county;
        }
    
        public String getProvince() {
            return province;
        }
    
        public void setProvince(String province) {
            this.province = province;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getCounty() {
            return county;
        }
    
        public void setCounty(String county) {
            this.county = county;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "province='" + province + '\'' +
                    ", city='" + city + '\'' +
                    ", county='" + county + '\'' +
                    '}';
        }
    }
    
    

    修改Person类

    package demo;
    import java.io.Serializable;
    public class Person implements Serializable {
        private static final long serialVersionUID = -7763700527072968555L;
        private String name;
        private Integer age;
        private double height;
        private Address address;
       //重点贴出修改的局部代码,其他保持不变
    

    修改测试程序PersonTest.java

    package demo;
    import java.io.*;
    class PersonTest {
        public static void main(String[] args) throws Exception {
            String filePath="C:\\test\\person.out";
            Address address = new Address("四川", "成都", "蒲江县");
            Person person = new Person("zhangsan", 21,1.80,address);
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化保存到文件中
            outputStream.writeObject(person);
            outputStream.close();
            //将文件中的字节还原为内存中的对象(反序列化)
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person) inputStream.readObject();
            inputStream.close();
            System.out.println(readObject);
        }
    }
    

    运行测试程序,结果如下

    Person的有参构造器
    Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
    

    控制序列化

    1. 对象中的字段可能因为某些原因,只需要序列化部分字段,例如,密码等字段由于安全原因不需要序列化,此时可以用transient关键字修饰不需要序列化的字段。修改前面的Person类
    package demo;
    import java.io.Serializable;
    public class Person implements Serializable {
        private static final long serialVersionUID = -7763700527072968555L;
        private  String name;
        private transient Integer age;
        private transient double height;
        private transient Address address;   
        //重点贴出修改的局部代码,其他保持不变   
    

    运行测试程序,结果如下:

    Person的有参构造器
    Person{name='zhangsan', age=null, height=0.0, address=null}
    

    可以观察到age,height,address字段没有被序列化保存到文件中,输出的值是均为其对应类型的默认值,并且transient关键字可以用在任何类型

    1. 精确控制序列化过程 Externalizable接口,其为Serializable的子接口,提供writeExternal(ObjectOutput out)控制序列化,readExternal(ObjectInput in)控制反序列化。修改Person类使其同时实现两个接口Serializable和Externalizable
    package demo;
    
    import java.io.*;
    
    public class Person implements Externalizable, Serializable {
    
    
        private static final long serialVersionUID = -7763700527072968555L;
        private  String name;
        private  Integer age;
        private  double height;
        private  Address address;
    
        public Person(String name, Integer age,double height,Address address) {
            System.out.println("Person的有参构造器");
            this.name = name;
            this.age = age;
            this.height=height;
            this.address=address;
        }
    
        public Person() {
        }
      //省略setter和getter方法  
    
        public void writeExternal(ObjectOutput out) throws IOException {
            //暂时没有实现
        }
    
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
           //暂时没有实现
        }
    }
    
    

    运行测试程序,结果如下:

    Person的有参构造器
    Person{name='null', age=null, height=0.0, address=null}
    

    结果可以看到Person对象在实现Externalizable接口没有反序列化成功,可以猜测若同时实现Externalizable和Serializable接口,以Externalizable接口为准

    重新修改Person对象,并添加必要的用于测试的方法,贴出修改部分

    package demo;
    import java.io.*;
    public class Person implements Externalizable {
        private String name;
        private Integer age;
        private double height;
        private Address address;
    
        public Person(String name, Integer age, double height, Address address) {      
            this.name = name;
            this.age = age;
            this.height = height;
            this.address = address;
        }
        
        public Person() {
            System.out.println("Person的无参构造器");
        }
    
        public void writeExternal(ObjectOutput out) throws IOException {
            //手动控制序列化过程
            out.writeObject(name);
            out.writeObject(age);
            out.writeDouble(height);
            out.writeObject(address);
    
        }
    
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            //序列化字段顺序需要和反序列化字段顺序一致
            name = ((String) in.readObject());
            age = ((Integer) in.readObject());
            height = in.readDouble();
            address = ((Address) in.readObject());
        }
    }
    
    

    运行测试程序,结果如下:

    Person的无参构造器
    Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
    

    从结果得出:1. 对象序列化和反序列化成功。2. Externalizable接口实现类对象在反序列化过程中会调用无参构造器

    修改Person对象序列化和反序列化方法,使其顺序不对应

      public void writeExternal(ObjectOutput out) throws IOException {
            //手动控制序列化过程
            out.writeObject(name);
            out.writeObject(age);
            out.writeDouble(height);
            out.writeObject(address);
        }
    
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            //手动控制反序列化过程
            name = ((String) in.readObject());
            age = ((Integer) in.readObject());
            address = ((Address) in.readObject());  //此处的address字段和height字段没有对应
            height = in.readDouble();
    
        }
    

    运行测试程序,结果如下:

    Person的无参构造器
    Exception in thread "main" java.io.OptionalDataException
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1365)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
        at demo.Person.readExternal(Person.java:81)
        at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1849)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1806)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
        at demo.PersonTest.main(PersonTest.java:16)
    

    从结果得出:1. Externalizable接口实现类对象反序列化过程首先是调用Person类的无参构造器,然后再根据保存的二进制数据还原成内存中的对象。2.序列化过程字段的顺序和反序列化过程字段顺序需要保持一致,否则会抛出异常

    Externalizable接口实现类在序列化过程中,只序列化部分字段,可以实现transient关键字的效果,当然也需要保持序列化和反序列化过程字段顺序一致

    对比Externalizable和Serializable接口可知:1. Serializable接口反序列化过程不会调用无参构造器,完全以二进制数据恢复对象。2.Externalizable会调用无参构造器,然后以将二进制的数据放入到生成的对象中

    1. 通过在Serializable接口中添加特定方法实现序列化和反序列化控制,修改Person类如下:

      package demo;
      import java.io.*;
      public class Person implements Serializable {
          
         private static final long serialVersionUID = -7763700527072968555L;
          private String name;
          private Integer age;
          private double height;
          private transient Address address;//address属性此时不能被序列化
      
          public Person(String name, Integer age, double height, Address address) {
              this.name = name;
              this.age = age;
              this.height = height;
              this.address = address;
          }
          public Person() {
              System.out.println("Person的无参构造器");
          }
          
         //添加的方法必须为这个签名
          private void writeObject(ObjectOutputStream stream)
                  throws IOException {
              stream.defaultWriteObject();//执行默认的递归序列化机制,不会序列化address属性
              stream.writeObject(address);//显示将transient关键字标记的字段序列化
          }
       //添加的方法必须为这个签名
          private void readObject(ObjectInputStream stream)
                  throws IOException, ClassNotFoundException {
              stream.defaultReadObject();
              address = (Address) stream.readObject();//显示将transient关键字标记的字段反序列化并赋值到相应的属性
          }
      }
      
      

      运行测试程序,结果如下:

      Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
      

      结果可以看到:1. 添加的方法在序列化和反序列化过程中会自动调用。2. 显示序列化的优先级高于transient关键字的优先级

      注意观察添加的两个方法,均是私有的private,按照开发习惯,通常这种标准的方法应该添加在接口中,但因为接口中的方法必须是public的,因此这两个方法不能添加到接口,在添加方法的时候要严格按照签名添加。此外,方法定义为private,意味着方法只能在类的内部调用,可是Person类中并没有调用,实际上Person类的writeObject调用是由java.io.ObjectStreamClass对象使用反射调用的

       void invokeWriteObject(Object obj, ObjectOutputStream out)
              throws IOException, UnsupportedOperationException
          {
              requireInitialized();
              if (writeObjectMethod != null) {
                  try {
                      writeObjectMethod.invoke(obj, new Object[]{ out }); //此处通过反射调用对象的writeObject方法
                  } catch (InvocationTargetException ex) {
                      Throwable th = ex.getTargetException();
                      if (th instanceof IOException) {
                          throw (IOException) th;
                      } else {
                          throwMiscException(th);
                      }
                  } catch (IllegalAccessException ex) {               
                      throw new InternalError(ex);
                  }
              } else {
                  throw new UnsupportedOperationException();
              }
          }
      

      writeObjectMethod则是在ObjectStreamClass对象的构造器中初始化:

      if (externalizable) {
          cons = getExternalizableConstructor(cl);
      } else {
          cons = getSerializableConstructor(cl);
          writeObjectMethod = getPrivateMethod(cl, "writeObject",
                                               new Class<?>[] { ObjectOutputStream.class },
                                               Void.TYPE); //此处通过反射获取writeObject方法对象,cl即为添加了writeObject方法的类的Class对象
          readObjectMethod = getPrivateMethod(cl, "readObject",
                                              new Class<?>[] { ObjectInputStream.class },
                                              Void.TYPE); //此处通过反射获取readObject方法对象,cl即为添加了readObject方法的类的Class对象
          readObjectNoDataMethod = getPrivateMethod(
              cl, "readObjectNoData", null, Void.TYPE);
          hasWriteObjectData = (writeObjectMethod != null);
      }
      

      ObjectStreamClass类是对可序列化类的描述,里面封装了一些描述属性,ObjectStreamClass对象的创建通过lookup方法

          private Class<?> cl;   //序列化类的Class对象
          private String name;  //序列化类的名字
          private volatile Long suid;  //序列化类的serialVersionUID
          private boolean isProxy;   //序列化类是否是动态代理类
          private boolean isEnum;  //序列化类是否是枚举
          private boolean serializable;  //序列化类是否实现了Serializable接口
          private boolean externalizable; //序列化类是否实现了Externalizable接口
          private Constructor<?> cons; //序列化适当的构造器,Serializable接口和Externalizable接口接口获取构造器的方式不同
      

    序列化版本serialVersionUID

    前面生成的Person类中存在一个静态字段serialVersionUID,用作序列化和反序列化的版本控制,只有版本相同时,才能将字节反序列化为内存中的对象,重新还原出对象的状态,若序列化字节中的serialVersionUID和类的serialVersionUID不一致,则会抛出异常。

    serialVersionUID可以显示指定,如 private static final long serialVersionUID = 8623666669972053776L;也可以不用指定,那么就会根据Java(TM) Object Serialization Specification规定的类的信息生成相应的serialVersionUID,一般都会显示指定serialVersionUID ,因为不同的编译器实现计算的serialVersionUID可能不同,进而导致抛出InvalidClassException异常。

    注释测试程序的反序列化部分,首先进行序列化

    package demo;
    import java.io.*;
    class PersonTest {
        public static void main(String[] args) throws Exception {
            String filePath="C:\\test\\person.out";
            Address address = new Address("四川", "成都", "蒲江县");
            Person person = new Person("zhangsan", 21,1.80,address);
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化保存到文件中
            outputStream.writeObject(person);
            outputStream.close();
            //将文件中的字节还原为内存中的对象(反序列化)
    //        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
    //        Person readObject = (Person) inputStream.readObject();
    //        inputStream.close();
    //        System.out.println(readObject);
        }
    }
    

    修改Person类的serialVersionUID,使其版本加一

    注释测试程序的序列化部分,进行反序列化

    package demo;
    import java.io.*;
    class PersonTest {
        public static void main(String[] args) throws Exception {
            String filePath="C:\\test\\person.out";
    //        Address address = new Address("四川", "成都", "蒲江县");
    //        Person person = new Person("zhangsan", 21,1.80,address);
    //        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
    //        //将对象序列化保存到文件中
    //        outputStream.writeObject(person);
    //        outputStream.close();
    //        将文件中的字节还原为内存中的对象(反序列化)
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person) inputStream.readObject();
            inputStream.close();
            System.out.println(readObject);
        }
    }
    

    运行测试程序,结果如下:

    Exception in thread "main" java.io.InvalidClassException: demo.Person; local class incompatible: stream classdesc serialVersionUID = 8623666669972053776, local class serialVersionUID = 8623666669972053777
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
        at demo.PersonTest.main(PersonTest.java:16)
    

    结果可以看出:抛出了InvalidClassException异常,并且提示指出两个类的版本不兼容(incompatible),同时给出了类的版本号

    序列化的常常用在远程调用中,将本地的对象序列化为字节,并通过网络传输给远端的一个机器,远端的机器通过反序列化还原成内存中的对象,实现数据的传递。现在考虑本地机器和远程机器在serialVersionUID不变的情况下,增减字段对序列化和反序列化的影响,serialVersionUID不一致无法序列化,因此不考虑。本地和远程增减字段总共4中情况,假设+表示增加字段,-表示减少字段,没有符号则表示字段不变,字段增减修改Person类

    1. 本地+sex,远程 (表示本地增加sex字段,远程版本字段保持不变):测试时先增加sex进行序列化,表示本地版本。然后删除sex进行反序列化,表示远程版本,测试结果如下:

      Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
      

      结果可以看出:序列化文件中多余的字段对反序列化没有影响,远程版本中没有相应的字段,自然没有字段值

    2. 本地,远程+sex:测试时,先进行序列化,表示本地版本,然后添加sex进行反序列化,表示远程版本

      Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}, sex='null'}
      

      结果可以看出:远程版本中多余的字段,由于序列化文件中没有相应的字段值,因此远程版本中的字段取其类型的默认值

    3. 本地-age,远程:测试时,先删除age并进行序列化,表示本地版本,然后增加age字段并进行反序列化,表示远程版本

      Person{name='zhangsan', age=null, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
      

      结果可以看出:由于本地序列化文件中缺少age字段,因此反序列化的类中age字段取默认值

    4. 本地,远程-age:测试时,先进行序列化,表示本地版本,然后删除age,进程反序列化,表示远程版本

      Person{name='zhangsan', height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
      

      结果可以看出:由于远程版本缺少age字段,因此即使序列化文件中有age值,反序列化后的类中任然没有age字段值

      综上:1. 只要版本一致,增减字段对序列化和反序列化过程没有影响。2.反序列化过程不会强制要求属性个数一致,若反序列化时,字段少了,自然就没有相应是字段值,若反序列化时,字段多了,多余的字段会取默认值。

    序列化使用注意事项

    1. 序列化保存的是对象的状态,因此静态变量不会被序列化,反序列自然也没有值。修改Person类,增加静态属性country

      package demo;
      import java.io.*;
      public class Person implements Serializable {
          private static final long serialVersionUID = 8623666669972053776L;
      
          public static String COUNTRY;
          private String name;
          private Integer age;
          private double height;
          private Address address;
       //省略未修改代码
      }
      
      

      修改测试程序并序列化

      package demo;
      import java.io.*;
      class PersonTest {
          public static void main(String[] args) throws Exception {
              String filePath = "C:\\test\\person.out";
              Address address = new Address("四川", "成都", "蒲江县");
              Person person = new Person("zhangsan", 21, 1.80, address);
              Person.COUNTRY = "中国";   //赋值静态属性
              ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
              outputStream.writeObject(person);
              outputStream.close();
          }
      }
      

      反序列化,结果如下:

      Person{name='zhangsan', age=21, height=1.8, country=null, address=Address{province='四川', city='成都', county='蒲江县'}}
      

      结果可以看出:country静态属性没有被序列化,取值为默认值

    2. 基本数据类型可以直接被序列化,引用类型数据需要实现Serializable或Externalizable接口,才能序列化

    3. 默认序列化机制会递归序列化,若对象还引用了其他对象,则其他对象也需要支持序列化,否则会抛出NotSerializableException,若不想序列化引用的对象,可以使用transient关键字

    4. 若一个类不支持序列化,但其父类支持序列化,则这个类也支持序列化。扩展Person类,增加Student类

      package demo;
      public class Student extends Person {
          private String schoolName;
          private String grade;
       //省略setter,getter和toString方法
      }
      

      增加测试程序StudentTest.java

      package demo;
      import java.io.*;
      class StudentTest {
          public static void main(String[] args) throws Exception {
              String filePath = "C:\\test\\student.out";
              Student student = new Student("七中", "高一");
              ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
              outputStream.writeObject(student);
              outputStream.close();
      
              ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
              Person readObject = (Person) inputStream.readObject();
              inputStream.close();
              System.out.println(readObject);
          }
      }
      

      测试结果如下:

      Person的无参构造器  
      Student{schoolName='七中', grade='高一'}
      

      结果可以看出:1. 若父类实现了序列化接口,则这类可以序列化(其实就是父类具有了某项功能,子类可通过继承,拥有其功能)

    5. 若子类可以序列化,但父类不能序列化,子类是可以序列化的。(java中所有类的最顶层父类都是Object,Object类不能序列化,但是String实现了Serializable接口,可以序列化)子类拥有的功能不受到父类的影响。

    6. 单例类在默认反序列化的时候,会被破坏,导致多个实例。创建单例类Company

      package demo;
      public class Compony implements Serializable{
          private static final long serialVersionUID = -7328519208950924476L;
          //饿汉式单例
          private static final Compony COMPONY = new Compony();
          private Compony() {
              //防止在类的外面new对象
              System.out.println("Compony的私有构造器");
          }
          //通过该静态方法对外暴露该类的唯一实例
          public static Compony getInstance() {
              return COMPONY;
          }
      }
      
      

      增加测试程序,测试反序列化后得到的Compony类是不是单例

      package demo;
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.ObjectInputStream;
      import java.io.ObjectOutputStream;
      class ComponyTest {
          public static void main(String[] args) throws Exception {
              String filePath = "C:\\test\\compony.out";
              Compony compony = Compony.getInstance();
              ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(filePath));
              outputStream.writeObject(compony);
              outputStream.close();
      
              ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
              Compony readObject = (Compony) inputStream.readObject();
              inputStream.close();
              System.out.println("Compony是否是单例类:" + (readObject == Compony.getInstance()));
          }
      }
      

      运行测试,结果如下:

      Compony是否是单例类:false
      

      从结果可以看出:1. 单例类在反序列化后被破坏,生成了一个新的对象。由于反序列化过程中会使用ObjectInputStream的readObject方法,因此从此方法追踪

      try {
          Object obj = readObject0(false);//生成对象
         //去除无关代码
          return obj;
      } finally {
          passHandle = outerHandle;
          if (closed && depth == 0) {
              clear();
          }
              }
      

      继续追踪:

         ObjectStreamClass desc = readClassDesc(false);
              desc.checkDeserialize(); //此时的desc对象即是对序列化类的描述对象
      
              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);
              }
      

      继续向下:

      Object newInstance()
              throws InstantiationException, InvocationTargetException,
                     UnsupportedOperationException
          {
           
              if (cons != null) {
                  try {
                      return cons.newInstance();   //此处便是核心的通过Constructor的newInstance反射创建对象
                  } catch (IllegalAccessException ex) {
                      // should not occur, as access checks have been suppressed
                      throw new InternalError(ex);
                  }
              } else {
                  throw new UnsupportedOperationException();
              }
          }      
      
      看到这里是否有疑问,Serializable实现类反序列化的时候不是不会调用构造器吗?怎么会通过构造器反射创建对象,实际代码输出也可以看出是没有调用无参构造器的,那此时这个cons是哪个的构造器呢?在此处断点调试 seralizable1.png

    可以看出此处的cons是Object的构造器,并不是Compony类的无参构造器

    cons的初始化代码如下

    //根据不同的接口实现类初始化cons
    if (externalizable) {
        cons = getExternalizableConstructor(cl); 
    } else {
        cons = getSerializableConstructor(cl);
       //去除无关代码    
    }
    

    综上:在反序列化的过程中,会调用cons.newInstance()生成一个新的对象,通过在序列化类中添加readResolve方法可以保证类的单例,修改Compony

    package demo;
    import java.io.Serializable;
    
    public class Compony implements Serializable {
     //省略未修改部分代码
        
        private Object readResolve() {
            return COMPONY;//返回值作为反序列化的对象
        }
    }
    
    

    运行测试程序

    Compony的私有构造器
    Compony是否是单例类:true
    

    结果可以看到:1. 单例类没有被破坏。观察Compony的readResolve方法也是私有的,类内部并没有调用,猜测也是在某处反射调用,入口依然是java.io.ObjectInputStream#readObject方法

    if (obj != null &&
                handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())//若类实现了serializable or externalizable接口,且定义了readResolve方法,则返回true
            {
                Object rep = desc.invokeReadResolve(obj);  //此处反射调用readResolve方法
                if (unshared && rep.getClass().isArray()) {
                    rep = cloneArray(rep);
                }
                if (rep != obj) {
                    handles.setObject(passHandle, obj = rep);
                }
            }
    

    继续向下追踪

     if (readResolveMethod != null) {
                try {
                    return readResolveMethod.invoke(obj, (Object[]) null);//此处正真反射调用readResolve方法
                } catch (InvocationTargetException ex) {
                   //去除无关代码
                } catch (IllegalAccessException ex) {              
                    throw new InternalError(ex);
                }
            } else {
                throw new UnsupportedOperationException();
            }
    

    readResolveMethod方法初始化代码:java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)

    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);//此处通过反射获取定义的readResolve方法
        return null;
                    }
    

    总结:单例类反序列化的时候需要注意单例类会被破坏,需要添加readResolve方法

    相关文章

      网友评论

          本文标题:java序列化,深度解析

          本文链接:https://www.haomeiwen.com/subject/cjkjaktx.html