實現
java.io.Serializable
接口, 會增加出錯和出現安全問題的可能性, 因為它開放了實例的另一種來源 ---- 反序列化. 有一種方法可以減少風險, 那就是序列化代理模式.
類圖如下:
序列化代理模式.png
小二, 上代碼 !
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 1. 序列化Person時, 會調用調用writeReplace()生成一個PersonProxy對象, 然后對此對象進行序列化 (不是對Person類對象進行序列化,
* 由序列化文件的內容可以得知, 可以查看序列化生成的文件, 文件中內容為如下圖 (代碼之后的圖)
* 2. 反序列化時, 會調用PersonProxy的readResolve()方法生成一個Person對象,
* 最后返回此對象的拷貝 (通過PersonProxy類的readResolve方法和main方法中的輸出可以看出)
* 3. 因此, Person類的序列化工作完全交給PersonProxy類, 正如此模式的名稱所表達的一樣
*/
public class Person implements Serializable {
private final String name;
private final String hobby;
private final int age;
public Person(String name, String hobby, int age) {
System.out.println("Person(String name, String hobby, int age)");
//約束條件
if(age < 0 || age > 200) {
throw new IllegalArgumentException("非法年齡");
}
this.name = name;
this.hobby = hobby;
this.age = age;
}
public String getName() {
return name;
}
public String getHobby() {
return hobby;
}
public int getAge() {
return age;
}
private static class PersonProxy implements Serializable {
private final String name;
private final String hobby;
private final int age;
public PersonProxy(Person original) {
System.out.println("PersonProxy(Person original)");
this.name = original.getName();
this.hobby = original.getHobby();
this.age = original.getAge();
}
private Object readResolve() {
System.out.println("PersonProxy.readResolve()");
Person person = new Person(name, hobby, age);
System.out.println("resolveObject: " + person);
return person;
}
}
private Object writeReplace() {
System.out.println("Person.writeReplace()");
return new PersonProxy(this); //readObject的時候是調用, PersonProxy的readResolve()
}
//此方法不會執行,
private void writeObject(ObjectOutputStream out) {
System.out.println("Person.writeObject()");
}
//防止攻擊者偽造數據, 企圖違反約束條件 (如: 違反年齡約束)
private Object readObject(ObjectInputStream in) throws InvalidObjectException {
System.out.println("Person.readObject()");
throw new InvalidObjectException("Proxy required");
}
}
序列化文件中的內容:
序列化文件內容
客戶程序如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Person person = new Person("張三", "足球" ,25);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serial.ser"));
out.writeObject(person);
out.flush();
out.close();
Thread.sleep(1000);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("serial.ser"));
Person deserPerson = (Person) in.readObject();
System.out.println("main: " + person);
in.close();
if(person == deserPerson) {
System.out.println("序列化前后是同一個對象");
} else {
//程序會走這一段, 反序列化會創建對象, 但是不會執行類的構造方法, 而是使用輸入流構造對象
System.out.println("序列化前后不是同一個對象, 哈哈哈");
}
}
}
此模式工作原理如下:
- 序列化Person時, 會調用調用writeReplace()生成一個PersonProxy對象, 然后對此對象進行序列化 (不是對Person類對象進行序列化, 由序列化文件的內容可以得知, 可以查看序列化生成的文件, 文件中內容為如下圖
序列化文件中的內容
- 反序列化時, 會調用PersonProxy的readResolve()方法生成一個Person對象, 最后返回此對象的拷貝 (通過PersonProxy類的readResolve方法和main方法中的輸出可以看出)
- 因此, Person類的序列化工作完全交給PersonProxy類, 正如此模式的名稱所表達的一樣
本文參考: 《Effective Java 第2版》第78條 『考慮使用序列化代理代替序列化實例』