对象序列化是什么?
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='蒲江县'}}
控制序列化
- 对象中的字段可能因为某些原因,只需要序列化部分字段,例如,密码等字段由于安全原因不需要序列化,此时可以用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关键字可以用在任何类型
- 精确控制序列化过程 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会调用无参构造器,然后以将二进制的数据放入到生成的对象中
-
通过在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类
-
本地+sex,远程 (表示本地增加sex字段,远程版本字段保持不变):测试时先增加sex进行序列化,表示本地版本。然后删除sex进行反序列化,表示远程版本,测试结果如下:
Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
结果可以看出:序列化文件中多余的字段对反序列化没有影响,远程版本中没有相应的字段,自然没有字段值
-
本地,远程+sex:测试时,先进行序列化,表示本地版本,然后添加sex进行反序列化,表示远程版本
Person{name='zhangsan', age=21, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}, sex='null'}
结果可以看出:远程版本中多余的字段,由于序列化文件中没有相应的字段值,因此远程版本中的字段取其类型的默认值
-
本地-age,远程:测试时,先删除age并进行序列化,表示本地版本,然后增加age字段并进行反序列化,表示远程版本
Person{name='zhangsan', age=null, height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
结果可以看出:由于本地序列化文件中缺少age字段,因此反序列化的类中age字段取默认值
-
本地,远程-age:测试时,先进行序列化,表示本地版本,然后删除age,进程反序列化,表示远程版本
Person{name='zhangsan', height=1.8, address=Address{province='四川', city='成都', county='蒲江县'}}
结果可以看出:由于远程版本缺少age字段,因此即使序列化文件中有age值,反序列化后的类中任然没有age字段值
综上:1. 只要版本一致,增减字段对序列化和反序列化过程没有影响。2.反序列化过程不会强制要求属性个数一致,若反序列化时,字段少了,自然就没有相应是字段值,若反序列化时,字段多了,多余的字段会取默认值。
序列化使用注意事项
-
序列化保存的是对象的状态,因此静态变量不会被序列化,反序列自然也没有值。修改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静态属性没有被序列化,取值为默认值
-
基本数据类型可以直接被序列化,引用类型数据需要实现Serializable或Externalizable接口,才能序列化
-
默认序列化机制会递归序列化,若对象还引用了其他对象,则其他对象也需要支持序列化,否则会抛出NotSerializableException,若不想序列化引用的对象,可以使用transient关键字
-
若一个类不支持序列化,但其父类支持序列化,则这个类也支持序列化。扩展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. 若父类实现了序列化接口,则这类可以序列化(其实就是父类具有了某项功能,子类可通过继承,拥有其功能)
-
若子类可以序列化,但父类不能序列化,子类是可以序列化的。(java中所有类的最顶层父类都是Object,Object类不能序列化,但是String实现了Serializable接口,可以序列化)子类拥有的功能不受到父类的影响。
-
单例类在默认反序列化的时候,会被破坏,导致多个实例。创建单例类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); }
继续向下:
看到这里是否有疑问,Serializable实现类反序列化的时候不是不会调用构造器吗?怎么会通过构造器反射创建对象,实际代码输出也可以看出是没有调用无参构造器的,那此时这个cons是哪个的构造器呢?在此处断点调试 seralizable1.pngObject 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(); } }
可以看出此处的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方法
网友评论