java序列化,深度解析
文章目錄
java序列化,深度解析
物件序列化是什麼?
java程式啟動會啟動一個相應的jvm程序,在程式執行期間,若新生成了物件,則會在jvm堆上分配空間(大多數情況),進行物件的表示,即物件的生命週期是短於jvm的,物件只能存在jvm程序執行時,但在某些情況下,我們希望將記憶體中的物件狀態(即物件的例項資料)儲存起來,儲存的地方可能是檔案,可能是資料庫,然後在將來的某一個時間讀取儲存的"物件"(此時是二進位制資料的物件),將其恢復成記憶體中的物件。java的序列化
如何使用?
若需要類支援序列化功能,只需實現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™ 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); }
繼續向下:
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是哪個的構造器呢?在此處斷點除錯
可以看出此處的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方法