Java 自定義序列化

問:說說你對(duì) Java 的 transient 關(guān)鍵字理解贾节?

答:對(duì)于不需要被序列化的屬性就可以通過加上 transient 關(guān)鍵字來處理火欧。一旦屬性被 transient 修飾就不再是對(duì)象持久化的一部分,該屬性的內(nèi)容在序列化后將無法獲得訪問振劳,transient 關(guān)鍵字只能修飾屬性變量成員而不能修飾方法和類(注意局部變量是不能被 transient 關(guān)鍵字修飾的)椎组,屬性成員如果是引用類型也需要保證實(shí)現(xiàn) Serializable 接口;此外在 Java 中對(duì)象的序列化可以通過實(shí)現(xiàn)兩種接口來實(shí)現(xiàn)历恐,若實(shí)現(xiàn)的是 Serializable 接口則所有的序列化將會(huì)自動(dòng)進(jìn)行寸癌,若實(shí)現(xiàn)的是 Externalizable 接口則沒有任何東西可以自動(dòng)序列化,需要在 writeExternal 方法中進(jìn)行手工指定所要序列化的變量弱贼,這與是否被 transient 修飾無關(guān)蒸苇。

問:對(duì)于 transient 修飾的屬性如何在不刪除修飾符的情況下讓其可以序列化?

答:本題其實(shí)就是在考察實(shí)現(xiàn) Serializable 接口情況下通過 writeObject() 與 readObject()方法進(jìn)行自定義序列化的機(jī)制吮旅。具體實(shí)現(xiàn)如下:

        public class Item {
            public String name;
            public String id;

            public School() {
            }

            public School(String name, String id) {
                this.name = name;
                this.id = id;
            }
        }
        public class Info implements Serializable { 
            ...
            transient private Item item = null; 
            ...

            private void writeObject(ObjectOutputStream out) throws IOException {
                //invoke default serialization method 
                out.defaultWriteObject();
                if (item == null) {
                    Item = new Item();
                }
                out.writeObject(item.name);
                out.writeObject(item.id);
            }

            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
                //invoke default serialization method 
                in.defaultReadObject();
                String name = (String) in.readObject();
                String id = (String) in.readObject();
                item = new Item(name, id);
            }
        }

上面在 writeObject() 方法中先調(diào)用了 ObjectOutputStream 的 defaultWriteObject() 方法溪烤,該方法會(huì)執(zhí)行默認(rèn)的序列化機(jī)制(忽略 item 字段),然后再調(diào)用 writeXXX() 方法顯示地將每個(gè)字段寫入到 ObjectOutputStream 中;readObject() 方法的作用是對(duì)象的讀取檬嘀,其原理與 writeObject() 方法相同槽驶。必須要注意的是 writeObject() 與 readObject() 都是 private 方法,其在 ObjectOutputStream 的 writeSerialData() 方法和 ObjectInputStream 的 readSerialData() 方法中通過反射進(jìn)行調(diào)用鸳兽。

問:簡單說說 Externalizable 與 Serializable 有什么區(qū)別掂铐?

答:使用 transient 還是用 writeObject() 和 readObject() 方法都是基于 Serializable 接口的序列化;JDK 提供的另一個(gè)序列化接口 Externalizable 繼承自 Serializable贸铜,使用該接口后基于 Serializable 接口的序列化機(jī)制就會(huì)失效(包括 transient堡纬,因?yàn)?Externalizable 不會(huì)主動(dòng)序列化),當(dāng)使用該接口時(shí)序列化的細(xì)節(jié)需要由我們自己去實(shí)現(xiàn)蒿秦,另外使用 Externalizable 主動(dòng)進(jìn)行序列化時(shí)當(dāng)讀取對(duì)象時(shí)會(huì)調(diào)用被序列化類的無參構(gòu)方法去創(chuàng)建一個(gè)新的對(duì)象烤镐,然后再將被保存對(duì)象的字段值分別填充到新對(duì)象中,所以實(shí)現(xiàn) Externalizable 接口的類必須提供一個(gè)無參 public 的構(gòu)造方法棍鳖。關(guān)于 Externalizable 的實(shí)例如下:

        public class Info implements Externalizable {
            private String name;
            private int age;

            public Info() {
            }//必須定義無參構(gòu)造方法

            public Info(String name, int age) {
                this.name = name;
                this.age = age;
            } //實(shí)現(xiàn)此方法反序列化時(shí)使用

            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
                this.name = (String) in.readObject();
                this.age = in.readInt();
            } //實(shí)現(xiàn)此方法序列化時(shí)使用 

            public void writeExternal(ObjectOutput out) throws IOException {
                out.writeObject(this.name);
                out.writeInt(this.age);
            }
        }

特別注意使用 Externalizable 方式時(shí)必須提供無參構(gòu)造方法炮叶,且 readExternal 方法必須按照與 writeExternal 方法寫入值時(shí)相同的順序和類型來讀取屬性值。

問:Serializable 序列化中自定義 readObjectNoData() 方法有什么作用渡处?

答:這個(gè)方法主要用來保證通過繼承擴(kuò)容后對(duì)老版本的兼容性镜悉,適用場景如下:比如類 Person 被序列化到硬盤后存儲(chǔ)為文件 old.txt,接著 Person 被修改繼承自 Animal医瘫,為了保證用新的 Person 反序列化老版本 old.txt 文件且 Animal 類的成員有默認(rèn)值則可以在 Animal 類中定義 readObjectNoData 方法返回成員的默認(rèn)值侣肄,具體可以參見 ObjectInputStream 類中的 readSerialData 方法判斷。

問:Java 序列化中 writeReplace() 方法有什么作用醇份?

答:Serializable 除了提供 writeObject 和 readObject 標(biāo)記方法外還提供了另外兩個(gè)標(biāo)記方法可以實(shí)現(xiàn)序列化對(duì)象的替換(即 writeReplace 和 readResolve)稼锅,序列化類一旦實(shí)現(xiàn)了 writeReplace 方法后則在序列化時(shí)就會(huì)先調(diào)用 writeReplace 方法將當(dāng)前對(duì)象替換成另一個(gè)對(duì)象(該方法會(huì)返回替換后的對(duì)象),接著系統(tǒng)將再次調(diào)用另一個(gè)對(duì)象的 writeReplace 方法僚纷,直到該方法不再返回另一個(gè)對(duì)象為止矩距,程序最后將調(diào)用該對(duì)象的 writeObject() 方法來保存該對(duì)象的狀態(tài)。通過下面例子可以說明上面這段話(AdapterBean 只是用來說明問題怖竭,實(shí)際應(yīng)用中可能是轉(zhuǎn)為 Map 或者列表等其他結(jié)構(gòu)):

class AdapterBean implements Serializable {
    private String name;
    private int age;

    public AdapterBean(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        System.out.println("AdapterBean writeObject.");
    }

    private void readObject(java.io.ObjectInputStream in) throws Exception {
        in.defaultReadObject();
        System.out.println("AdapterBean readObject.");
    }
}


class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        System.out.println("Person writeObject.");
    }

    private void readObject(java.io.ObjectInputStream in) throws Exception {
        in.defaultReadObject();
        System.out.println("Person readObject.");
    }

    private Object writeReplace() throws ObjectStreamException {
        System.out.println("Person writeReplace.");
        return new AdapterBean(name, age);
    }
}

public class Main {
    public static void main(String[] args) throws IOException, Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.serial"));
        Person p = new Person("工匠若水", 27);
        oos.writeObject(p);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.serial"));
        System.out.println(((AdapterBean) ois.readObject()).toString());
    }
}

上面程序的輸出結(jié)果如下:
Person writeReplace.
AdapterBean writeObject.
AdapterBean readObject.
AdapterBean@24459efb

特別說明锥债,實(shí)現(xiàn)了 writeReplace 的序列化類就不要再實(shí)現(xiàn) writeObject 了,因?yàn)樵擃惖?writeObject 方法就不會(huì)被調(diào)用了痊臭;實(shí)現(xiàn) writeReplace 的返回對(duì)象必須是可序列話的對(duì)象哮肚;通過 writeReplace 序列化替換的對(duì)象在反序列化中無論實(shí)現(xiàn)哪個(gè)方法都是無法恢復(fù)原對(duì)象的(即對(duì)象被徹底替換了),也就是說使用 ObjectInputStream 讀取的對(duì)象只能是被替換后的對(duì)象广匙,要想恢復(fù)只能在讀取后自己手動(dòng)構(gòu)造恢復(fù)绽左;所以 writeObject 只和 readObject 配合使用,一旦實(shí)現(xiàn)了 writeReplace 在寫入時(shí)進(jìn)行替換就不再需要 writeObject 和 readObject 了艇潭,故替換就是徹底的自定義了,比 writeObject 和 readObject 自定義更徹底。

問:Java 序列化中 readResolve() 方法有什么作用蹋凝?

答:同上 Serializable 除過提供了 writeObject 和 readObject 標(biāo)記方法外還提供了另外兩個(gè)標(biāo)記方法可以實(shí)現(xiàn)序列化對(duì)象的替換(即 writeReplace 和 readResolve)鲁纠,readResolve() 方法可以實(shí)現(xiàn)保護(hù)性復(fù)制整個(gè)對(duì)象,緊挨著序列化類實(shí)現(xiàn)的 readObject() 之后被調(diào)用鳍寂,該方法的返回值會(huì)代替原來反序列化的對(duì)象改含,而原來序列化類中 readObject() 反序列化的對(duì)象將會(huì)立即丟棄。readObject() 方法在序列化單例類時(shí)尤其有用迄汛,單例序列化都應(yīng)該提供 readResolve() 方法捍壤,這樣才可以保證反序列化的對(duì)象依然正常。同理給個(gè)直觀例子如下:

final class Singleton implements Serializable {
    private Singleton() {
    }

    private static final Singleton INSTANCE = new Singleton();

    public static Singleton getInstance() {
        return INSTANCE;
    }

    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

public class Main {
    public static void main(String[] args) throws IOException, Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.serial"));
        Singleton p = Singleton.getInstance();
        oos.writeObject(p);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.serial"));
        Singleton p1 = (Singleton) ois.readObject();
        System.out.println(p == p1);
    }
}

上面代碼運(yùn)行結(jié)果為 true鞍爱,如果去掉 readResolve 實(shí)現(xiàn)則結(jié)果為 false鹃觉。

所以 readResolve 方法可以保護(hù)性恢復(fù)對(duì)象(同時(shí)也可以替換對(duì)象),調(diào)用該方法之前會(huì)先調(diào)用序列化類的 readObject 反序列化得到對(duì)象睹逃,在該方法中可以正常通過 this 訪問到剛才反序列化得到的對(duì)象內(nèi)容盗扇,然后可以根據(jù)這些內(nèi)容進(jìn)行一定處理返回一個(gè)對(duì)象,所以其最重要的應(yīng)用就是保護(hù)性恢復(fù)單例對(duì)象(當(dāng)然使用枚舉類的單例就天生支持此特性)沉填。

問:Java 序列化存儲(chǔ)傳輸為什么不安全疗隶?怎么解決?

答:因?yàn)樾蛄谢M(jìn)制格式完全編寫在文檔中且完全可逆翼闹,所以只需將二進(jìn)制序列化流的內(nèi)容轉(zhuǎn)儲(chǔ)到控制臺(tái)就可以看清類及其包含的內(nèi)容斑鼻,故序列化對(duì)象中的任何 private 字段幾乎都是以明文的方式出現(xiàn)在序列化流中,如果我們傳輸?shù)男蛄谢瘮?shù)據(jù)中途被截獲猎荠,截獲方通過反序列化就可以獲得里面的數(shù)據(jù)(敏感數(shù)據(jù)的泄露)坚弱,甚至對(duì)里面的數(shù)據(jù)進(jìn)行修改然后發(fā)送給接收方(無法確保數(shù)據(jù)來源的安全性),從而產(chǎn)生了序列化安全問題法牲。

要解決序列化安全問題的核心原理就是避免在序列化中傳遞敏感數(shù)據(jù)史汗,所以可以使用關(guān)鍵字 transient 修飾敏感數(shù)據(jù)的變量,或者通過自定義序列化相關(guān)流程對(duì)數(shù)據(jù)進(jìn)行簽名加密機(jī)制再存儲(chǔ)或者傳輸(最簡單譬如女生年齡可以在序列化時(shí)進(jìn)行移位操作拒垃,反序列化時(shí)進(jìn)行反向移位復(fù)原操作停撞,或者使用一些加密算法處理)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悼瓮,一起剝皮案震驚了整個(gè)濱河市戈毒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌横堡,老刑警劉巖埋市,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異命贴,居然都是意外死亡道宅,警方通過查閱死者的電腦和手機(jī)食听,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來污茵,“玉大人樱报,你說我怎么就攤上這事∨⒌保” “怎么了迹蛤?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長襟士。 經(jīng)常有香客問我盗飒,道長,這世上最難降的妖魔是什么陋桂? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任逆趣,我火速辦了婚禮,結(jié)果婚禮上章喉,老公的妹妹穿的比我還像新娘汗贫。我一直安慰自己,他們只是感情好秸脱,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布落包。 她就那樣靜靜地躺著,像睡著了一般摊唇。 火紅的嫁衣襯著肌膚如雪咐蝇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天巷查,我揣著相機(jī)與錄音有序,去河邊找鬼。 笑死岛请,一個(gè)胖子當(dāng)著我的面吹牛旭寿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播崇败,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼盅称,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了后室?” 一聲冷哼從身側(cè)響起缩膝,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岸霹,沒想到半個(gè)月后疾层,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贡避,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年痛黎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了予弧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湖饱,死狀恐怖桌肴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琉历,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布水醋,位于F島的核電站旗笔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拄踪。R本人自食惡果不足惜蝇恶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惶桐。 院中可真熱鬧撮弧,春花似錦、人聲如沸姚糊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽救恨。三九已至贸辈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肠槽,已是汗流浹背擎淤。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秸仙,地道東北人嘴拢。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像寂纪,于是被迫代替她去往敵國和親席吴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,872評(píng)論 0 24
  • 正如前文《Java序列化心得(一):序列化設(shè)計(jì)和默認(rèn)序列化格式的問題》中所提到的弊攘,默認(rèn)序列化方法存在各種各樣的問題...
    登高且賦閱讀 8,428評(píng)論 0 19
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化抢腐,必須實(shí)現(xiàn)Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,414評(píng)論 1 3
  • 序列化的意義 1.永久存儲(chǔ)某個(gè)jvm中運(yùn)行時(shí)的對(duì)象襟交。2.對(duì)象可以網(wǎng)絡(luò)傳輸3.rmi調(diào)用都是以序列化的方式傳輸參數(shù) ...
    炫邁哥閱讀 656評(píng)論 0 0
  • 將一個(gè)對(duì)象編碼成字節(jié)流稱作將該對(duì)象「序列化」捣域。相反啼染,從字節(jié)流編碼中重新構(gòu)建對(duì)象被稱作「反序列化」宴合。一旦對(duì)象被「序列...
    Alent閱讀 783評(píng)論 0 1