青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

Shuffy

不斷的學習,不斷的思考,才能不斷的進步.Let's do better together!
posts - 102, comments - 43, trackbacks - 0, articles - 19

數年前,當和一個軟件團隊一起用 Java 語言編寫一個應用程序時,我體會到比一般程序員多知道一點關于 Java 對象序列化的知識所帶來的好處。

關于本系列

您覺得自己懂 Java 編程?事實上,大多數程序員對于 Java 平臺都是淺嘗則止,只學習了足以完成手頭上任務的知識而已。在本 系列 中,Ted Neward 深入挖掘 Java 平臺的核心功能,揭示一些鮮為人知的事實,幫助您解決最棘手的編程挑戰。

大約一年前,一個負責管理應用程序所有用戶設置的開發人員,決定將用戶設置存儲在一個 Hashtable 中,然后將這個 Hashtable 序列化到磁盤,以便持久化。當用戶更改設置時,便重新將 Hashtable 寫到磁盤。

這是一個優雅的、開放式的設置系統,但是,當團隊決定從 Hashtable 遷移到 Java Collections 庫中的 HashMap 時,這個系統便面臨崩潰。

HashtableHashMap 在磁盤上的格式是不相同、不兼容的。除非對每個持久化的用戶設置運行某種類型的數據轉換實用程序(極其龐大的任務),否則以后似乎只能一直用 Hashtable 作為應用程序的存儲格式。

團隊感到陷入僵局,但這只是因為他們不知道關于 Java 序列化的一個重要事實:Java 序列化允許隨著時間的推移而改變類型。當我向他們展示如何自動進行序列化替換后,他們終于按計劃完成了向 HashMap 的轉變。

本文是本系列的第一篇文章,這個系列專門揭示關于 Java 平臺的一些有用的小知識 — 這些小知識不易理解,但對于解決 Java 編程挑戰遲早有用。

將 Java 對象序列化 API 作為開端是一個不錯的選擇,因為它從一開始就存在于 JDK 1.1 中。本文介紹的關于序列化的 5 件事情將說服您重新審視那些標準 Java API。

Java 序列化簡介

Java 對象序列化是 JDK 1.1 中引入的一組開創性特性之一,用于作為一種將 Java 對象的狀態轉換為字節數組,以便存儲或傳輸的機制,以后,仍可以將字節數組轉換回 Java 對象原有的狀態。

實際上,序列化的思想是 “凍結” 對象狀態,傳輸對象狀態(寫到磁盤、通過網絡傳輸等等),然后 “解凍” 狀態,重新獲得可用的 Java 對象。所有這些事情的發生有點像是魔術,這要歸功于 ObjectInputStream/ObjectOutputStream 類、完全保真的元數據以及程序員愿意用 Serializable 標識接口標記他們的類,從而 “參與” 這個過程。

清單 1 顯示一個實現 SerializablePerson 類。


清單 1. Serializable Person
				
package com.tedneward;

public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }

    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }

    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }

    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + spouse.getFirstName() +
            "]";
    }    

    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;

}

Person 序列化后,很容易將對象狀態寫到磁盤,然后重新讀出它,下面的 JUnit 4 單元測試對此做了演示。


清單 2. 對 Person 進行反序列化
				
public class SerTest
{
    @Test public void serializeToDisk()
    {
        try
        {
            com.tedneward.Person ted = new com.tedneward.Person("Ted", "Neward", 39);
            com.tedneward.Person charl = new com.tedneward.Person("Charlotte",
                "Neward", 38);

            ted.setSpouse(charl); charl.setSpouse(ted);

            FileOutputStream fos = new FileOutputStream("tempdata.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(ted);
            oos.close();
        }
        catch (Exception ex)
        {
            fail("Exception thrown during test: " + ex.toString());
        }
        
        try
        {
            FileInputStream fis = new FileInputStream("tempdata.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            com.tedneward.Person ted = (com.tedneward.Person) ois.readObject();
            ois.close();
            
            assertEquals(ted.getFirstName(), "Ted");
            assertEquals(ted.getSpouse().getFirstName(), "Charlotte");

            // Clean up the file
            new File("tempdata.ser").delete();
        }
        catch (Exception ex)
        {
            fail("Exception thrown during test: " + ex.toString());
        }
    }
}

到現在為止,還沒有看到什么新鮮的或令人興奮的事情,但是這是一個很好的出發點。我們將使用 Person 來發現您可能 知道的關于 Java 對象序列化 的 5 件事。


1. 序列化允許重構

序列化允許一定數量的類變種,甚至重構之后也是如此,ObjectInputStream 仍可以很好地將其讀出來。

Java Object Serialization 規范可以自動管理的關鍵任務是:

  • 將新字段添加到類中
  • 將字段從 static 改為非 static
  • 將字段從 transient 改為非 transient

取決于所需的向后兼容程度,轉換字段形式(從非 static 轉換為 static 或從非 transient 轉換為 transient)或者刪除字段需要額外的消息傳遞。

重構序列化類

既然已經知道序列化允許重構,我們來看看當把新字段添加到 Person 類中時,會發生什么事情。

如清單 3 所示,PersonV2 在原先 Person 類的基礎上引入一個表示性別的新字段。


清單 3. 將新字段添加到序列化的 Person 中
				
enum Gender
{
    MALE, FEMALE
}

public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a, Gender g)
    {
        this.firstName = fn; this.lastName = ln; this.age = a; this.gender = g;
    }
  
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public Gender getGender() { return gender; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }

    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setGender(Gender value) { gender = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }

    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " gender=" + gender +
            " age=" + age +
            " spouse=" + spouse.getFirstName() +
            "]";
    }    

    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
    private Gender gender;
}

序列化使用一個 hash,該 hash 是根據給定源文件中幾乎所有東西 — 方法名稱、字段名稱、字段類型、訪問修改方法等 — 計算出來的,序列化將該 hash 值與序列化流中的 hash 值相比較。

為了使 Java 運行時相信兩種類型實際上是一樣的,第二版和隨后版本的 Person 必須與第一版有相同的序列化版本 hash(存儲為 private static final serialVersionUID 字段)。因此,我們需要 serialVersionUID 字段,它是通過對原始(或 V1)版本的 Person 類運行 JDK serialver 命令計算出的。

一旦有了 PersonserialVersionUID,不僅可以從原始對象 Person 的序列化數據創建 PersonV2 對象(當出現新字段時,新字段被設為缺省值,最常見的是“null”),還可以反過來做:即從 PersonV2 的數據通過反序列化得到 Person,這毫不奇怪。


2. 序列化并不安全

讓 Java 開發人員詫異并感到不快的是,序列化二進制格式完全編寫在文檔中,并且完全可逆。實際上,只需將二進制序列化流的內容轉儲到控制臺,就足以看清類是什么樣子,以及它包含什么內容。

這對于安全性有著不良影響。例如,當通過 RMI 進行遠程方法調用時,通過連接發送的對象中的任何 private 字段幾乎都是以明文的方式出現在套接字流中,這顯然容易招致哪怕最簡單的安全問題。

幸運的是,序列化允許 “hook” 序列化過程,并在序列化之前和反序列化之后保護(或模糊化)字段數據。可以通過在 Serializable 對象上提供一個 writeObject 方法來做到這一點。

模糊化序列化數據

假設 Person 類中的敏感數據是 age 字段。畢竟,女士忌談年齡。我們可以在序列化之前模糊化該數據,將數位循環左移一位,然后在反序列化之后復位。(您可以開發更安全的算法,當前這個算法只是作為一個例子。)

為了 “hook” 序列化過程,我們將在 Person 上實現一個 writeObject 方法;為了 “hook” 反序列化過程,我們將在同一個類上實現一個 readObject 方法。重要的是這兩個方法的細節要正確 — 如果訪問修改方法、參數或名稱不同于清單 4 中的內容,那么代碼將不被察覺地失敗,Person 的 age 將暴露。


清單 4. 模糊化序列化數據
				
public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }

    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }
    
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }

    private void writeObject(java.io.ObjectOutputStream stream)
        throws java.io.IOException
    {
        // "Encrypt"/obscure the sensitive data
        age = age << 2;
        stream.defaultWriteObject();
    }

    private void readObject(java.io.ObjectInputStream stream)
        throws java.io.IOException, ClassNotFoundException
    {
        stream.defaultReadObject();

        // "Decrypt"/de-obscure the sensitive data
        age = age << 2;
    }
    
    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + (spouse!=null ? spouse.getFirstName() : "[null]") +
            "]";
    }      

    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
}

如果需要查看被模糊化的數據,總是可以查看序列化數據流/文件。而且,由于該格式被完全文檔化,即使不能訪問類本身,也仍可以讀取序列化流中的內容。


3. 序列化的數據可以被簽名和密封

上一個技巧假設您想模糊化序列化數據,而不是對其加密或者確保它不被修改。當然,通過使用 writeObjectreadObject 可以實現密碼加密和簽名管理,但其實還有更好的方式。

如果需要對整個對象進行加密和簽名,最簡單的是將它放在一個 javax.crypto.SealedObject 和/或 java.security.SignedObject 包裝器中。兩者都是可序列化的,所以將對象包裝在 SealedObject 中可以圍繞原對象創建一種 “包裝盒”。必須有對稱密鑰才能解密,而且密鑰必須單獨管理。同樣,也可以將 SignedObject 用于數據驗證,并且對稱密鑰也必須單獨管理。

結合使用這兩種對象,便可以輕松地對序列化數據進行密封和簽名,而不必強調關于數字簽名驗證或加密的細節。很簡潔,是吧?


4. 序列化允許將代理放在流中

很多情況下,類中包含一個核心數據元素,通過它可以派生或找到類中的其他字段。在此情況下,沒有必要序列化整個對象??梢詫⒆侄螛擞洖?transient,但是每當有方法訪問一個字段時,類仍然必須顯式地產生代碼來檢查它是否被初始化。

如果首要問題是序列化,那么最好指定一個 flyweight 或代理放在流中。為原始 Person 提供一個 writeReplace 方法,可以序列化不同類型的對象來代替它。類似地,如果反序列化期間發現一個 readResolve 方法,那么將調用該方法,將替代對象提供給調用者。

打包和解包代理

writeReplacereadResolve 方法使 Person 類可以將它的所有數據(或其中的核心數據)打包到一個 PersonProxy 中,將它放入到一個流中,然后在反序列化時再進行解包。


清單 5. 你完整了我,我代替了你
				
class PersonProxy
    implements java.io.Serializable
{
    public PersonProxy(Person orig)
    {
        data = orig.getFirstName() + "," + orig.getLastName() + "," + orig.getAge();
        if (orig.getSpouse() != null)
        {
            Person spouse = orig.getSpouse();
            data = data + "," + spouse.getFirstName() + "," + spouse.getLastName() + ","  
              + spouse.getAge();
        }
    }

    public String data;
    private Object readResolve()
        throws java.io.ObjectStreamException
    {
        String[] pieces = data.split(",");
        Person result = new Person(pieces[0], pieces[1], Integer.parseInt(pieces[2]));
        if (pieces.length > 3)
        {
            result.setSpouse(new Person(pieces[3], pieces[4], Integer.parseInt
              (pieces[5])));
            result.getSpouse().setSpouse(result);
        }
        return result;
    }
}

public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }

    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }

    private Object writeReplace()
        throws java.io.ObjectStreamException
    {
        return new PersonProxy(this);
    }
    
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }   

    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + spouse.getFirstName() +
            "]";
    }    
    
    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
}

注意,PersonProxy 必須跟蹤 Person 的所有數據。這通常意味著代理需要是 Person 的一個內部類,以便能訪問 private 字段。有時候,代理還需要追蹤其他對象引用并手動序列化它們,例如 Person 的 spouse。

這種技巧是少數幾種不需要讀/寫平衡的技巧之一。例如,一個類被重構成另一種類型后的版本可以提供一個 readResolve 方法,以便靜默地將被序列化的對象轉換成新類型。類似地,它可以采用 writeReplace 方法將舊類序列化成新版本。


5. 信任,但要驗證

認為序列化流中的數據總是與最初寫到流中的數據一致,這沒有問題。但是,正如一位美國前總統所說的,“信任,但要驗證”。

對于序列化的對象,這意味著驗證字段,以確保在反序列化之后它們仍具有正確的值,“以防萬一”。為此,可以實現 ObjectInputValidation 接口,并覆蓋 validateObject() 方法。如果調用該方法時發現某處有錯誤,則拋出一個 InvalidObjectException。


結束語

Java 對象序列化比大多數 Java 開發人員想象的更靈活,這使我們有更多的機會解決棘手的情況。

幸運的是,像這樣的編程妙招在 JVM 中隨處可見。關鍵是要知道它們,在遇到難題的時候能用上它們。

5 件事 系列下期預告:Java Collections。在此之前,好好享受按自己的想法調整序列化吧!



下載

描述 名字 大小 下載方法
本文樣例代碼 5things1-src.zip 10KB HTTP

關于下載方法的信息


參考資料

學習

討論

關于作者

Ted Neward 的照片

Ted Neward 是一家全球咨詢公司 ThoughtWorks 的顧問,也是 Neward & Associates 的總裁,從事關于 Java、.NET、XML Services 以及其他平臺方面的咨詢、指導和演示等工作。他居住在華盛頓西雅圖。

原文地址:http://www.ibm.com/developerworks/cn/java/j-5things1/index.html?ca=drs-cn-0504

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲国产精品一区二区三区| 亚洲福利精品| 亚洲午夜性刺激影院| 日韩视频一区二区三区在线播放| 欧美大色视频| 亚洲国产激情| 99国产精品99久久久久久| 妖精视频成人观看www| 亚洲深夜福利视频| 午夜精品福利一区二区三区av| 亚洲欧美国产不卡| 久久久国产一区二区| 欧美成人精品1314www| 欧美日韩亚洲高清| 国产综合久久久久久鬼色| 亚洲成人直播| 亚洲天堂av图片| 欧美影院精品一区| 欧美激情亚洲视频| 日韩视频免费大全中文字幕| 亚洲欧美日韩久久精品| 开心色5月久久精品| 国产精品播放| 亚洲高清一区二| 亚洲一区二区精品| 猛干欧美女孩| 亚洲午夜一区二区三区| 久久综合国产精品台湾中文娱乐网| 欧美大胆人体视频| 国产在线成人| 亚洲欧美美女| 亚洲国产欧美在线人成| 亚洲欧美日韩爽爽影院| 欧美精品v日韩精品v国产精品| 国产日韩欧美成人| 亚洲一级在线观看| 亚洲电影免费观看高清完整版在线观看 | 欧美日韩免费看| 狠狠色丁香久久婷婷综合_中| 91久久久亚洲精品| 久久精品一区蜜桃臀影院| 99av国产精品欲麻豆| 久久手机免费观看| 国产欧美婷婷中文| 校园春色综合网| 9久re热视频在线精品| 欧美成人午夜激情在线| 国产一区日韩二区欧美三区| 亚洲欧美国产va在线影院| 国内在线观看一区二区三区| 一本色道久久综合精品竹菊| 欧美jizz19性欧美| 久久久久国产精品麻豆ai换脸| 国产精品无码永久免费888| 亚洲天堂男人| 亚洲免费高清| 欧美日韩精品一区| 99精品国产高清一区二区 | 亚洲激情第一页| 久久裸体艺术| 欧美一区二区三区啪啪| 国产精品视频精品| 欧美亚洲午夜视频在线观看| 这里是久久伊人| 国产精品日韩欧美| 欧美一区二区性| 欧美专区日韩专区| 精品999在线观看| 免费h精品视频在线播放| 久久人人爽国产| 91久久极品少妇xxxxⅹ软件| 欧美国产一区二区三区激情无套| 久久永久免费| 99综合电影在线视频| 亚洲精品社区| 国产精品裸体一区二区三区| 欧美尤物巨大精品爽| 性欧美精品高清| 1769国内精品视频在线播放| 亚洲丁香婷深爱综合| 欧美日韩一区在线观看视频| 亚洲在线观看视频网站| 午夜精彩视频在线观看不卡 | 欧美国内亚洲| 亚洲一区欧美二区| 欧美一区二区观看视频| 亚洲国产日韩在线一区模特| 亚洲伦伦在线| 国产一区二区三区免费不卡 | 欧美成人精品h版在线观看| 久久一二三区| 亚洲一区二区精品| 欧美综合77777色婷婷| 最新国产乱人伦偷精品免费网站| 最新高清无码专区| 国产日韩一区二区三区在线播放 | 欧美二区视频| 国产精品久久国产三级国电话系列 | 亚洲黄网站在线观看| 亚洲在线观看视频| 久久国产99| 亚洲视频999| 久久久久久国产精品mv| 亚洲精品视频在线| 午夜日韩在线| 一区二区三区日韩精品视频| 小处雏高清一区二区三区| 99av国产精品欲麻豆| 欧美亚洲日本网站| 亚洲女同精品视频| 欧美日本在线一区| 欧美成人69av| 国产主播一区二区| 中文久久乱码一区二区| 亚洲美女黄网| 老巨人导航500精品| 久久精品国产清高在天天线| 欧美日韩在线另类| 亚洲国产欧美在线| 亚洲第一搞黄网站| 久久不见久久见免费视频1| 亚洲女人小视频在线观看| 欧美日本精品| 亚洲国产国产亚洲一二三| 一区二区三区在线免费视频| 亚洲亚洲精品三区日韩精品在线视频 | 久久婷婷久久| 欧美诱惑福利视频| 国产精品久久久久久模特| 亚洲欧洲日产国码二区| 亚洲三级免费| 免费永久网站黄欧美| 免费成人你懂的| 国产综合在线看| 欧美一区三区三区高中清蜜桃| 午夜欧美不卡精品aaaaa| 国产精品高清网站| 一本色道久久综合一区| 中国日韩欧美久久久久久久久| 欧美激情精品久久久久久| 亚洲人成亚洲人成在线观看图片| 亚洲精品一区二区在线| 欧美华人在线视频| 99精品热视频只有精品10| 亚洲一区自拍| 国产麻豆视频精品| 久久国产夜色精品鲁鲁99| 久久久噜噜噜久久| 在线看日韩欧美| 欧美~级网站不卡| 亚洲精品中文字幕在线| 亚洲一区二区精品在线| 国产精品视频免费| 久久久久久久综合日本| 欧美激情一区二区三区成人| 99视频一区二区| 国产精品亚洲综合一区在线观看| 香蕉乱码成人久久天堂爱免费 | 一本大道久久a久久综合婷婷| 亚洲午夜免费视频| 国产日韩欧美日韩大片| 久久在线播放| 亚洲毛片一区| 久久精品电影| 亚洲精品乱码久久久久| 亚洲午夜激情| 久久国产婷婷国产香蕉| 在线成人免费视频| 欧美人与性动交a欧美精品| 亚洲一级影院| 女仆av观看一区| 亚洲一区二区在线免费观看视频| 国产日本欧美一区二区三区| 久久伊人免费视频| 在线午夜精品自拍| 欧美高清在线一区二区| 亚洲欧美日本日韩| 亚洲第一天堂av| 国产精品免费一区二区三区观看| 久久久xxx| 亚洲一区二区三区777| 欧美成人嫩草网站| 欧美在线免费| 亚洲图片自拍偷拍| 在线免费一区三区| 国产精品欧美精品| 欧美激情综合五月色丁香小说| 午夜国产精品视频| 99在线热播精品免费99热| 欧美成人一区二免费视频软件| 亚洲亚洲精品在线观看 | 制服丝袜激情欧洲亚洲| 国产综合自拍| 国产精品久久久久av| 欧美77777| 久久久久久久久久看片| 亚洲欧美视频在线观看| 99热这里只有精品8| 亚洲国产欧美不卡在线观看| 美国成人直播|