序列化最基本使用
public class A implements Serializable{
private static final long serialVersionUID = 9175036933185692367L;
private final String name;
private String Id;
public A(String name,String Id){
this.name = name;
this.Id = Id;
} public void methodA(){
System.out.println("My name is zazalu" + Id);
}
}
class B{
public static void main(String[] args) throws IOException {
//將A序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("test.txt")));
A a = new A("zazaluA","123456");
oos.writeObject(a);
}
}
class C{
//將A反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("test.txt")));
A a = (A) ois.readObject();
a.methodA(); //輸出My name is zazalu123456
}
}
以上就是最基本的序列化使用方式政模,不多說堕伪。接下來來思考Serializable到底在底層發(fā)生了什么
其實弹沽,上面的代碼其實隱式調(diào)用了2個方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
接下來我們來演示下窜锯,將這兩個方法在A類中重寫
private void readObject(ObjectInputStream in){
System.out.println("調(diào)用了readObject");
}
private void writeObject(java.io.ObjectOutputStream out){
System.out.println("調(diào)用了writeObject");
}
然后我們在此運行下熏挎,控制臺輸出就變成了這樣:
可見蚌成,這兩個方法都被調(diào)用了前痘。
那么接下來我們就要學習這兩個方法到底可以做什么?
首先我們先了解下下面兩個方法
in.defaultReadObject();
out.defaultWriteObject(); //in和out是對象輸入輸出流的兩個實例
這兩個方法其實是對象流讀寫對象的默認的底層調(diào)用方法担忧。
當我們使用out.writeObject()和in.readObject()的時候芹缔,就會默認去調(diào)用
in.defaultReadObject();
out.defaultWriteObject();
這兩個方法是會把允許序列化的屬性和方法進行序列化和反序列化(被transient關(guān)鍵字修飾的屬性方法是不會被默認序列化的)
既然
in.defaultReadObject();
out.defaultWriteObject();
這兩個是默認的方法,那么看來我們可以自定義序列化過程
如何自定義序列化瓶盛,其實方法有很多
主要是使用
private void readObject(ObjectInputStream in){
System.out.println("只反序列化Id屬性");
out.writeObject(Id);
}
private void writeObject(java.io.ObjectOutputStream out){
System.out.println("只序列化Id屬性");
this.Id = in.readObject();
}
在序列化的類中重寫這兩個方法進行自定義序列化最欠!上述寫法就只序列化了一個Id屬性示罗,而name屬性沒有序列化!
但是我們這樣自定義序列化是很粗糙的芝硬,因為我們沒有處理很多細節(jié)方法的問題蚜点,比如NotActiveException我們就沒有做處理。所以我們最好是
in.defaultReadObject();
out.defaultWriteObject();
讓這兩個方法被調(diào)用吵取。所以我們可以如此實現(xiàn)
1.先將所有屬性方法喲哦那個transient關(guān)鍵字修飾
2.將你想要序列化的字段用out.writeObject()和in.readObject()來實現(xiàn)
3.在最后調(diào)用in.defaultReadObject(); 和in.defaultReadObject(); 來保證細節(jié)處理正常
private void readObject(ObjectInputStream in){
System.out.println("只反序列化Id屬性");
out.writeObject(Id);
in.defaultReadObject();
}
private void writeObject(java.io.ObjectOutputStream out){
System.out.println("只序列化Id屬性");
this.Id = in.readObject();
in.defaultReadObject();
}
序列化安全問題
1.首先禽额,序列化出來的字節(jié)流,可以進行分析皮官,從而給攻擊者暴露了所有的信息脯倒,解決方法:在序列化時對字段進行加密,反序列化的時候再進行解密
2.其次捺氢,攻擊者可以偽造字節(jié)流藻丢,然后通過反序列化獲取珍貴的信息。
比如(Period是一個類摄乒,有start和end這兩個Date屬性)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(/*new FileOutputStream(new File("D:/obj.data"))*/bos);
oos.writeObject(new Period(new Date(), new Date()));
/*
偽造一個字節(jié)流悠反,這個字節(jié)流以一個有效的Period實例所產(chǎn)生的字節(jié)流作為開始,然后附加上兩個額外的引用馍佑,指向Period實例中的兩個內(nèi)部私有Date域斋否,攻擊者通過引用攻擊內(nèi)部域
*/
byte[] ref = {0x71, 0 , 0x7e, 0, 5};
bos.write(ref);
ref[4] = 4;
bos.write(ref);
ObjectInputStream ois = new ObjectInputStream(/*new FileInputStream(new File("D:/obj.data"))*/new ByteArrayInputStream(bos.toByteArray()));
period = (Period)ois.readObject();
start = (Date)ois.readObject(); //獲取到了Period里的start屬性引用
end = (Date)ois.readObject(); //獲取到了Period里的end屬性引用
......
//獲取到的end屬性,我們可以修改拭荤,因為是對象類型茵臭,所以period里的end屬性也發(fā)生了變化!這實在是太惡毒了
end.setYear(78);
解決辦法:使用保護性拷貝舅世!
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException{
in.defaultReadObject();
start = new Date(start.getTime());
end = new Date(end.getTime()); //重新創(chuàng)建start和end對象引用
}
可是這辦法因為二次修改了屬性的引用旦委,所以無法和final字段配合使用,更多時候我們會希望屬性是final的雏亚,這樣就不會被輕易修改了缨硝。
加強解決辦法:使用序列化代理
序列化代理
http://blog.csdn.net/drifterj/article/details/7802586
參看這個bolg,寫的非常具體了罢低!