java序列化,深度解析

对象序列化是什么?

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序列化,深度解析)