Java中如何自定義序列化 掌握writeObject

自定義序列化是指通過實(shí)現(xiàn)writeobject和readobject方法,由開發(fā)者決定Java對(duì)象如何轉(zhuǎn)換為字節(jié)流及如何還原。1. 要實(shí)現(xiàn)自定義序列化,需讓類實(shí)現(xiàn)serializable接口,并定義private的writeobject和readobject方法以控制序列化過程;2. transient關(guān)鍵字用于標(biāo)記不參與默認(rèn)序列化的字段,但可通過自定義方法手動(dòng)處理;3. 為解決版本兼容性問題,應(yīng)使用serialversionuid標(biāo)識(shí)版本,并在結(jié)構(gòu)變更時(shí)更新其值;4. 另一種方式是實(shí)現(xiàn)externalizable接口,通過writeexternal和readexternal方法完全手動(dòng)控制序列化,同時(shí)必須提供無參構(gòu)造函數(shù);5. 避免安全漏洞的方法包括避免序列化敏感數(shù)據(jù)、使用安全庫(kù)、對(duì)數(shù)據(jù)簽名或加密、限制反序列化類并及時(shí)更新庫(kù)。掌握自定義序列化機(jī)制有助于更靈活、安全地處理對(duì)象持久化與傳輸需求。

Java中如何自定義序列化 掌握writeObject

自定義序列化,簡(jiǎn)單來說,就是讓你自己來決定Java對(duì)象怎么轉(zhuǎn)換成字節(jié)流,以及如何從字節(jié)流還原成對(duì)象。writeObject 方法是實(shí)現(xiàn)自定義序列化的關(guān)鍵。

Java中如何自定義序列化 掌握writeObject

解決方案

要自定義序列化,你需要讓你的類實(shí)現(xiàn) java.io.Serializable 接口。這只是一個(gè)標(biāo)記接口,告訴jvm這個(gè)類的對(duì)象可以被序列化。然后,你需要在類中定義一個(gè) private void writeObject(java.io.ObjectOutputStream out) throws IOException 方法和一個(gè) private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException 方法。

Java中如何自定義序列化 掌握writeObject

writeObject 方法負(fù)責(zé)將對(duì)象的狀態(tài)寫入 ObjectOutputStream,而 readObject 方法負(fù)責(zé)從 ObjectInputStream 讀取狀態(tài)并恢復(fù)對(duì)象。

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

一個(gè)簡(jiǎn)單的例子:

Java中如何自定義序列化 掌握writeObject

import java.io.*;  public class MyObject implements Serializable {      private String name;     private int age;     private transient String secret; // transient 關(guān)鍵字,不參與默認(rèn)序列化      public MyObject(String name, int age, String secret) {         this.name = name;         this.age = age;         this.secret = secret;     }      public String getName() {         return name;     }      public int getAge() {         return age;     }      public String getSecret() {         return secret;     }      private void writeObject(ObjectOutputStream out) throws IOException {         // 先執(zhí)行默認(rèn)的序列化         out.defaultWriteObject();          // 自定義序列化 secret 字段         String encodedSecret = encrypt(secret); // 假設(shè)encrypt方法存在         out.writeObject(encodedSecret);     }      private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {         // 先執(zhí)行默認(rèn)的反序列化         in.defaultReadObject();          // 自定義反序列化 secret 字段         String encodedSecret = (String) in.readObject();         this.secret = decrypt(encodedSecret); // 假設(shè)decrypt方法存在     }      private String encrypt(String data) {         // 簡(jiǎn)單的加密示例,實(shí)際應(yīng)用中需要更安全的加密算法         return new StringBuilder(data).reverse().toString();     }      private String decrypt(String data) {         // 簡(jiǎn)單的解密示例         return new StringBuilder(data).reverse().toString();     }      public static void main(String[] args) throws IOException, ClassNotFoundException {         MyObject obj = new MyObject("Alice", 30, "MySecret");          // 序列化         ByteArrayOutputStream bos = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(bos);         oos.writeObject(obj);         oos.close();          // 反序列化         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());         ObjectInputStream ois = new ObjectInputStream(bis);         MyObject deserializedObj = (MyObject) ois.readObject();         ois.close();          System.out.println("Name: " + deserializedObj.getName());         System.out.println("Age: " + deserializedObj.getAge());         System.out.println("Secret: " + deserializedObj.getSecret()); // 解密后的 secret     } }

為什么需要自定義序列化?

默認(rèn)的序列化機(jī)制可能不滿足所有需求。比如,你可能想加密某些敏感數(shù)據(jù),或者排除某些字段不被序列化(使用 transient 關(guān)鍵字)。自定義序列化允許你完全控制序列化的過程。另外,如果你的對(duì)象包含一些非Serializable的字段,你必須使用自定義序列化來處理這些字段。

transient 關(guān)鍵字的作用是什么?

transient 關(guān)鍵字用于標(biāo)記不應(yīng)該被序列化的字段。當(dāng)一個(gè)字段被標(biāo)記為 transient,默認(rèn)的序列化機(jī)制會(huì)忽略它。這意味著在反序列化時(shí),該字段的值將是其類型的默認(rèn)值(例如,NULL 對(duì)于對(duì)象類型,0 對(duì)于 int 類型)。在上面的例子中,secret 字段被標(biāo)記為 transient,即使沒有自定義序列化,它也不會(huì)被默認(rèn)序列化。但通過自定義的 writeObject 和 readObject 方法,我們?nèi)匀豢梢钥刂扑男蛄谢头葱蛄谢?/p>

如何處理序列化中的版本兼容性問題?

當(dāng)類的結(jié)構(gòu)發(fā)生變化時(shí),例如添加、刪除或修改字段,可能會(huì)導(dǎo)致序列化版本不兼容。為了解決這個(gè)問題,你可以使用 serialVersionUID。serialVersionUID 是一個(gè)靜態(tài)常量,用于標(biāo)識(shí)類的序列化版本。

private static final long serialVersionUID = 1L;

如果類的結(jié)構(gòu)發(fā)生變化,你應(yīng)該更新 serialVersionUID 的值。這樣,當(dāng)嘗試反序列化舊版本的對(duì)象時(shí),JVM會(huì)檢測(cè)到版本不匹配,并拋出 InvalidClassException 異常。

如果你希望兼容舊版本,可以謹(jǐn)慎地添加或刪除字段,并確保 readObject 方法能夠正確處理舊版本的數(shù)據(jù)。通常,添加字段是相對(duì)安全的,但刪除字段可能會(huì)導(dǎo)致反序列化失敗。

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {     in.defaultReadObject();     try {         // 嘗試讀取新字段         this.newField = in.readObject();     } catch (java.io.OptionalDataException e) {         // 如果舊版本沒有這個(gè)字段,則忽略異常         if (!e.eof) throw e;         this.newField = null; // 設(shè)置為默認(rèn)值     } }

除了writeObject和readObject,還有其他自定義序列化的方式嗎?

除了 writeObject 和 readObject 方法,還可以實(shí)現(xiàn) Externalizable 接口。Externalizable 接口繼承自 Serializable 接口,但它提供了更強(qiáng)的控制權(quán)。當(dāng)你實(shí)現(xiàn) Externalizable 接口時(shí),你需要實(shí)現(xiàn) writeExternal 和 readExternal 方法。

import java.io.*;  public class MyExternalizable implements Externalizable {      private String name;     private int age;      public MyExternalizable() {         // 必須提供一個(gè)無參構(gòu)造函數(shù)     }      public MyExternalizable(String name, int age) {         this.name = name;         this.age = age;     }      public String getName() {         return name;     }      public int getAge() {         return age;     }      @Override     public void writeExternal(ObjectOutput out) throws IOException {         out.writeObject(name);         out.writeInt(age);     }      @Override     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {         this.name = (String) in.readObject();         this.age = in.readInt();     }      public static void main(String[] args) throws IOException, ClassNotFoundException {         MyExternalizable obj = new MyExternalizable("Bob", 40);          // 序列化         ByteArrayOutputStream bos = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(bos);         oos.writeObject(obj);         oos.close();          // 反序列化         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());         ObjectInputStream ois = new ObjectInputStream(bis);         MyExternalizable deserializedObj = (MyExternalizable) ois.readObject();         ois.close();          System.out.println("Name: " + deserializedObj.getName());         System.out.println("Age: " + deserializedObj.getAge());     } }

實(shí)現(xiàn) Externalizable 接口時(shí),需要注意以下幾點(diǎn):

  • 必須提供一個(gè)無參構(gòu)造函數(shù)。在反序列化時(shí),JVM會(huì)先調(diào)用無參構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象,然后再調(diào)用 readExternal 方法恢復(fù)對(duì)象的狀態(tài)。
  • 你需要手動(dòng)序列化和反序列化所有字段,包括父類的字段。
  • Externalizable 接口提供了更大的靈活性,但也需要更多的代碼。

如何避免序列化中的安全漏洞?

序列化和反序列化可能會(huì)引入安全漏洞,例如反序列化漏洞。攻擊者可以構(gòu)造惡意的序列化數(shù)據(jù),導(dǎo)致在反序列化時(shí)執(zhí)行任意代碼。

為了避免這些漏洞,可以采取以下措施:

  • 盡量避免序列化敏感數(shù)據(jù)。
  • 使用安全的序列化庫(kù),例如 json 或 Protocol Buffers。
  • 對(duì)序列化數(shù)據(jù)進(jìn)行簽名或加密,以防止篡改。
  • 限制可以反序列化的類,使用白名單機(jī)制。
  • 定期更新序列化庫(kù),以修復(fù)已知的安全漏洞。

總而言之,理解并掌握 Java 中的自定義序列化機(jī)制,特別是 writeObject 方法,對(duì)于編寫健壯、安全、可維護(hù)的應(yīng)用程序至關(guān)重要。它允許你精確控制對(duì)象的序列化和反序列化過程,從而滿足各種復(fù)雜的需求。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊13 分享