Java Entity實體中serialVersionUID的作用

在實際項目開發中,肯定在實體類中見到過serialVersionUID所聲明的常量,有沒有想過這個serialVersionUID有什麼作用呢?

一、實現Serializable接口的作用

類實現Serializable接口的目的是爲了對象可持久化,比如網絡傳輸或本地存儲,爲系統的分佈和異構部署提供先決條件支持。若沒有序列化,現在熟悉的遠程調用、對象數據庫都不可能存在;

二、序列化與反序列化代碼示例

  • 首先通過一個簡單的代碼示例JavaBean實現Serializable接口;
import java.io.Serializable;
public class Person implements Serializable {
    private String name;

	private Integer age;

   //setter/getter省略...
}
  • 定義一個消息生產者類(Producer)將Person類序列化到本地存儲/讀取:
public class Producer {
    public static void main(String[] args) {
        Person p = new Person();
        p.setName("張三");
        p.setAge(21);
        // 序列化,保存到磁盤上
        SerializationUtils.writeObject(p);
    }
}
  • 工具類SerializationUtils對一個類進行序列化和反序列化,並存儲到硬盤上(模擬網絡傳輸),代碼如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializationUtils {
    private static String FILE_NAME = "d:/obj.bin";
    //序列化
    public static void writeObject(Serializable s) {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
            oos.writeObject(s);
            oos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //反序列化
    public static Object readObject() {
        Object obj = null;
        try {
            ObjectInputStream input = new ObjectInputStream(new FileInputStream(FILE_NAME));
            obj=input.readObject();
            input.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return obj;
    }
}
  • 序列化存儲操作後,在d盤可以看到序列化後的文件:
    在這裏插入圖片描述
  • 定義一個消息消費者類(Producer)將磁盤上的文件反序列化後獲取數據:
public class Customer {
    public static void main(String[] args) {
        //反序列化
        Person p=(Person) SerializationUtils.readObject();
        System.out.println(p.getName());
    }
}
  • 這是一個反序列化的過程,也就是對象數據流轉換爲一個實例的過程,其運行後的輸出結果爲 “張三,21”,這就是序列化和反序列化的示例:
    在這裏插入圖片描述
  • 思考一個問題,如果消息生產者消息消費者(Person類)的中成員變量屬性有差異,比如:在分佈式部署應用中,一個應用中的消息生產者中的Person類添加一個性別屬性,而另一個應用中的消費者沒有增加性別屬性。消息發送通過中間件廣播方式發消息的情況,漏掉一兩個訂閱者也是很正常的。在這種情況下會發生什麼?

三、生產者與消費者中實體屬性不一致

1.不聲明serializableUID,生產者成員變量(有sex變量)>消費者成員變量(無sex變量)

  • 生產者Person類::
public class Person implements Serializable {
    private String name;

    private Integer age;

    private String sex;

   //setter/getter省略...
}
  • 生產者代碼:
public class Producer {
    public static void main(String[] args) {
        Person p = new Person();
        p.setName("張三");
        p.setAge(21);
        p.setSex("男");
        // 序列化,保存到磁盤上
        SerializationUtils.writeObject(p);
    }
}
  • 消費者Person類:
public class Person implements Serializable {
	private String name;

    private Integer age;

   //setter/getter省略...
 }
  • 消費者代碼:
public class Customer {
    public static void main(String[] args) {
        //反序列化
        Person p = (Person) SerializationUtils.readObject();
        System.out.println(p.getName() + " , " + p.getAge());
    }
}
  • 結果報了一個InalidClassException異常,原因是序列化和反序列化所對應的類版本發生了變化,JVM不能把數據流轉換爲實例對象:
    在這裏插入圖片描述
  • JVM是根據SerializableUID來判斷一個類的版本,SerializableUID也叫做流標識符(Stream Unique Identifier),即類的版本定義的,它可以顯示聲明也可以隱式聲明
    1. 隱示聲明:編譯器在編譯的時候生成。生成依據通過包名、類名、繼承關係、非私有的方法和屬性,以及參數、返回值等諸多因子算出來的,極度複雜,基本上計算出來的這個值是唯一的。
    2. 顯示聲明格式如下(可以避免對象的不一致導致反序列化報錯):
private static final long serialVersionUID = 7111078431122519129L; 
  • serialVersionUID的作用:JVM在反序列化時,會比較數據流中的serialVersionUID與消費者類的serialVersionUID是否相同,如果相同,則認爲類沒有改變,反序列化成功;如果不相同,JVM就會拋出InviladClassException異常。這是一個非常好的校驗機制,可以保證一個對象即使在網絡或磁盤中“滾過”一次,仍能做到“出淤泥而不染”,完美的實現了類的一致性。
  • 但是,有時候我們需要一點特例場景,例如我的類改變不大,JVM是否可以把以前的對象反序列化回來?就是依據顯示聲明的serialVersionUID,向JVM撒謊說"類版本沒有變化",如此編寫的類就實現了向上兼容

2.聲明serializableUID,生產者成員變量(有sex變量)>消費者成員變量(無sex變量)

修改一下示例代碼,在生產者消費者的Person類中同時添加private static final long serialVersionUID = 7111078431122519129L;

  • 生產者Person類 :
public class Person implements Serializable {
	//添加serialVersionUID 
    private static final long serialVersionUID = 7111078431122519129L;

    private String name;

    private Integer age;

    private String sex;

	//setter/getter省略...
 }   
  • 生產代碼:
public class Producer {
    public static void main(String[] args) {
        Person p = new Person();
        p.setName("張三");
        p.setAge(21);
        p.setSex("男");
        // 序列化,保存到磁盤上
        SerializationUtils.writeObject(p);
    }
}
  • 消費者Person類:
public class Person implements Serializable {
	//添加serialVersionUID 
    private static final long serialVersionUID = 7111078431122519129L;

    private String name;

    private Integer age;

	//setter/getter省略...
 }   
  • 消費者代碼:
public class Customer {
    public static void main(String[] args) {
        //反序列化
        Person p = (Person) SerializationUtils.readObject();
        System.out.println(p.getName() + " , " + p.getAge());
    }
}
  • 看消費者運行結果(這裏的生產者Person中sex屬性,消費者Person中沒有sex屬性):
    在這裏插入圖片描述

3.聲明serializableUID,生產者成員變量(無age變量、sex變量)<消費者成員變量(有age變量、sex變量)

一起來看看,當生產者成員變量(無age變量、sex變量)<消費者成員變量(有age變量、sex變量)時,會出現什麼情況:

  • 生產者Person類:
public class Person implements Serializable {
    private static final long serialVersionUID = 7111078431122519129L;

    private String name;
    
//setter/getter省略...
}
  • 生產者代碼:
public class Producer {
    public static void main(String[] args) {
        Person p = new Person();
        p.setName("張三");
        // 序列化,保存到磁盤上
        SerializationUtils.writeObject(p);
    }
}
  • 消費者Person類:
public class Person implements Serializable {
    private static final long serialVersionUID = 7111078431122519129L;

    private String name;

    private Integer age;

    private String sex;
    
//setter/getter省略...
  • 消費者代碼:
public class Customer {
    public static void main(String[] args) {
        //反序列化
        Person p = (Person) SerializationUtils.readObject();
        System.out.println(p.getName() + " , " + p.getAge()+ " , " +p.getSex());
    }
}
  • 反序列化操作成功,運行結果,age變量和sex變量在消費者端反序列化後被自動賦值爲null
    在這裏插入圖片描述
  • 從示例2、示例3結果可以看出,聲明瞭serialVersionUID後,消費者程序並沒有在拋出InalidClassException異常,而是可以正常完成反序列化操作,只是沒有接收到sex屬性值或自動填充不存在的值。剛開始生產者和消費者持有的Person類一致,都是V1.0,某天生產者的Person類變更了,增加了一個“性別”屬性,升級爲V2.0,由於種種原因(比如程序員疏忽,升級時間窗口不同等)消費端的Person類還是V1.0版本,添加的代碼爲 priavte String sex;以及對應的setter和getter方法。此時雖然生產者和消費者對應的類版本不同,但是顯示聲明的serialVersionUID相同,這時的反序列化是可運行的!!! 所帶來的業務問題就是消費端不能讀取到新增的業務屬性(sex屬性而已)。通過此例,反序列化也實現了版本向上兼容的功能,使用V1.0版本的應用訪問了一個V2.0的對象,這無疑提高了代碼的健壯性。所以在編寫序列化類代碼時隨手添加一個serialVersionUID字段,也不會帶來太多的工作量,但它卻可以在關鍵時候發揮異乎尋常的作用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章