序列化最基本使用
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
}
}
以上就是最基本的序列化使用方式,不多說(shuō)。接下來(lái)來(lái)思考Serializable到底在底層發(fā)生了什么
其實(shí),上面的代碼其實(shí)隱式調(diào)用了2個(gè)方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
接下來(lái)我們來(lái)演示下,將這兩個(gè)方法在A類(lèi)中重寫(xiě)
private void readObject(ObjectInputStream in){
System.out.println("調(diào)用了readObject");
}
private void writeObject(java.io.ObjectOutputStream out){
System.out.println("調(diào)用了writeObject");
}
然后我們?cè)诖诉\(yùn)行下,控制臺(tái)輸出就變成了這樣:
可見(jiàn),這兩個(gè)方法都被調(diào)用了。
那么接下來(lái)我們就要學(xué)習(xí)這兩個(gè)方法到底可以做什么?
首先我們先了解下下面兩個(gè)方法
in.defaultReadObject();
out.defaultWriteObject(); //in和out是對(duì)象輸入輸出流的兩個(gè)實(shí)例
這兩個(gè)方法其實(shí)是對(duì)象流讀寫(xiě)對(duì)象的默認(rèn)的底層調(diào)用方法。
當(dāng)我們使用out.writeObject()和in.readObject()的時(shí)候,就會(huì)默認(rèn)去調(diào)用
in.defaultReadObject();
out.defaultWriteObject();
這兩個(gè)方法是會(huì)把允許序列化的屬性和方法進(jìn)行序列化和反序列化(被transient關(guān)鍵字修飾的屬性方法是不會(huì)被默認(rèn)序列化的)
既然
in.defaultReadObject();
out.defaultWriteObject();
這兩個(gè)是默認(rèn)的方法,那么看來(lái)我們可以自定義序列化過(guò)程
如何自定義序列化,其實(shí)方法有很多
主要是使用
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();
}
在序列化的類(lèi)中重寫(xiě)這兩個(gè)方法進(jìn)行自定義序列化!上述寫(xiě)法就只序列化了一個(gè)Id屬性,而name屬性沒(méi)有序列化!
但是我們這樣自定義序列化是很粗糙的,因?yàn)槲覀儧](méi)有處理很多細(xì)節(jié)方法的問(wèn)題,比如NotActiveException我們就沒(méi)有做處理。所以我們最好是
in.defaultReadObject();
out.defaultWriteObject();
讓這兩個(gè)方法被調(diào)用。所以我們可以如此實(shí)現(xiàn)
1.先將所有屬性方法喲哦那個(gè)transient關(guān)鍵字修飾
2.將你想要序列化的字段用out.writeObject()和in.readObject()來(lái)實(shí)現(xiàn)
3.在最后調(diào)用in.defaultReadObject(); 和in.defaultReadObject(); 來(lái)保證細(xì)節(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();
}
序列化安全問(wèn)題
1.首先,序列化出來(lái)的字節(jié)流,可以進(jìn)行分析,從而給攻擊者暴露了所有的信息,解決方法:在序列化時(shí)對(duì)字段進(jìn)行加密,反序列化的時(shí)候再進(jìn)行解密
2.其次,攻擊者可以偽造字節(jié)流,然后通過(guò)反序列化獲取珍貴的信息。
比如(Period是一個(gè)類(lèi),有start和end這兩個(gè)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()));
/*
偽造一個(gè)字節(jié)流,這個(gè)字節(jié)流以一個(gè)有效的Period實(shí)例所產(chǎn)生的字節(jié)流作為開(kāi)始,然后附加上兩個(gè)額外的引用,指向Period實(shí)例中的兩個(gè)內(nèi)部私有Date域,攻擊者通過(guò)引用攻擊內(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屬性,我們可以修改,因?yàn)槭菍?duì)象類(lèi)型,所以period里的end屬性也發(fā)生了變化!這實(shí)在是太惡毒了
end.setYear(78);
解決辦法:使用保護(hù)性拷貝!
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對(duì)象引用
}
可是這辦法因?yàn)槎涡薷牧藢傩缘囊茫詿o(wú)法和final字段配合使用,更多時(shí)候我們會(huì)希望屬性是final的,這樣就不會(huì)被輕易修改了。
加強(qiáng)解決辦法:使用序列化代理
序列化代理
http://blog.csdn.net/drifterj/article/details/7802586
參看這個(gè)bolg,寫(xiě)的非常具體了!