對序列化進行刨根問底

咱們編寫一個實現了Serializable接口(序列化標誌接口)的類,Eclipse立刻就會給一個黃色警告:須要增長一個Serial Version ID.爲何要增長?它是怎麼計算出來的?有什麼用?下面咱們來解釋一下。java

類實現Serializable接口的目的是爲了可持續化,好比網絡傳輸或本地存儲,爲系統的分佈和異構部署提供先決支持條件。如果沒有序列化,如今咱們熟悉的遠程調用、對象數據庫都不可能存在,咱們來看一個簡單的序列化類:數據庫

public class Person implements Serializable{網絡

private String name;分佈式

/*name屬性的get/set方法省略*/
工具

}
對象

這是一個簡單的javabean,實現了Serializable接口,能夠在網絡上傳輸,也能夠本地存儲而後讀取。這裏咱們以java的消息服務方式傳遞對象(即經過網絡傳遞一個對象),定義在消息隊列中的數據類型爲ObjectMessage,首先定義一個消息的生產者(Producer),代碼以下:繼承

public class Producer{接口

public static void main(String[] args) throws Exception{隊列

Person person=new Person();事件

person.setName("死神");

//序列化,保存在磁盤上

SerializationUtils.writeObject(person);

}

}

這裏引入了一個工具類SerializationUtils,其做用是對一個類進行序列化和反序列化,並存儲到硬盤上,其代碼以下:

public class SerializationUtils{

private static String FILE_NAME="c:\obj.bin";

//序列化

public static void writeObject(Serializable s){

try{

    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(FILE_NAME));

    oos.writeObject(s);

    oos.close();

}catch(Exception e){

e.printStackTrace();

}

}

public static Object readObject(){

    Object obj=null;

//反序列化

try{

    ObjectInput input=new ObjectInputStream(new FileInputStream(FILE_NAME));

obj=input.readObject();

input.close();

}catch(Exception e){

    e.printStackTrace();

}

return obj;

}

}

經過對象序列化過程,把一個對象從內存塊轉化爲可傳輸的數據流,而後經過網絡發送到消費者那裏,並進行序列化,生成實例對象,代碼以下:

public class Consumer{

public static void main(String[] args){

//反序列化

Person p=new (Person) SerializationUtils.readObject();

System,out,println("name="+p.getName);

}

}

這是一個反序列化的過程,也就是對象數據流轉換爲一個實例對象的過程,其運行後的輸出結果爲:死神。這很容易,是的,這就一個很簡單的序列化和反序列化的demo。但此處隱藏一個問題:若是消息的生產者和消息的消費者所參考的類(Person)有差別,會出現何種神奇的事件呢?好比:消息的生產者中的Person類增長一個年齡的屬性,而消費者沒有增長該屬性。爲何沒有增長,由於這是分佈式部署的應用。,你甚至都不知道這個應用部署在何處,特別是經過廣播方式發送消息的狀況,漏掉一兩個訂閱者也是很正常的。

在這種序列化和反序列化的類不一致的 情形下,反序列化就會報一個InvalidClassException異常,緣由是序列化和反序列化所對應的類版本發生了變化,JVM不能把數據流轉換爲實例對象。接着刨根問底:JVM是根據什麼來判斷一個類版本的呢?

好問題,經過SerialVersionUID,也叫作流標識符,即類的版本定義的,它能夠顯示申明也能夠隱式申明。顯示申明格式以下:

private static final long serialVersionUID=xxxxxxL;

而隱式申明則是我不申明。你編譯器在編譯的時候幫我生成。生成的依據是經過包名、類名、繼承關係、非私有的方法和屬性,以及參數、返回值等諸多因子計算得出的,極度複雜,基本上計算出來的這個值是惟一的。

    serialVersionUID 如何生成已經說明了,那咱們再來看看serialVersionUID的做用。JVM在反序列化時,會比較數據流中的serialVersionUID與類的serialversionUID是否相同,若是相同,則認爲類沒有發生改變,能夠把數據流load爲實例對象,若是不相同,對不起,我JVM不幹了,拋出個異常InvalidClassExeception給你瞧瞧。這是一個很是好的校驗機制,能夠保證一個對象即便在網絡或磁盤中國「滾過」一次,任然能作到「出淤泥而不染」,完美的實現類的一致性。

 可是,有時候咱們須要一點特例場景,例如:個人類改變不大,JVM撒謊說「個人 版本沒有變動」,如此,咱們編寫的類就實現了向上兼容。咱們修改一下上面的Person類,代碼以下:

public class Person implements Serializable{

private static final long serialVersonUID =55799L;

/*其它保持不變*/

剛開始生產者和消費者持有Person類版本一致,都是V1.0,某天生產者的Person類版本變動了,增長了一個年齡的屬性,升級爲V2.0版本,而因爲種種緣由消費者端的Person仍是之前的版本,代碼以下:

public class Person implements Serializable{

private static final long serialVersionUID =5799L;

/*age、name的get和set方法省略*/


}

}

此時雖然生產者和消費者對應的類版本不一樣,可是顯示聲明的serialVersionUID相同,反序列化也是能夠運行的,所帶來的業務問題就是消費者端不能讀取新增的業務屬性(age而已)

經過此例,咱們的反序列化實現了版本的向上兼容的功能,因此咱們在編寫序列化類代碼時,隨手加上serialversionUID字段,也不會給咱們帶來太多的工做量,但它卻能夠在關鍵的時候發揮異乎尋常的做用。

相關文章
相關標籤/搜索