亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫(kù)
最近訪問(wèn)板塊 發(fā)新帖
查看: 1959 | 回復(fù): 1
打印 上一主題 下一主題

java serializable深入了解 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2012-03-19 17:33 |只看該作者 |倒序?yàn)g覽
java serializable深入了解








serializable深入了解

引言

將 Java 對(duì)象序列化為二進(jìn)制文件的 Java 序列化技術(shù)是 Java 系列技術(shù)中一個(gè)較為重要的技術(shù)點(diǎn),在大部分情況下,開(kāi)發(fā)人員只需要了解被序列化的類(lèi)需要實(shí)現(xiàn) Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 進(jìn)行對(duì)象的讀寫(xiě)。然而在有些情況下,光知道這些還遠(yuǎn)遠(yuǎn)不夠,文章列舉了筆者遇到的一些真實(shí)情境,它們與 Java 序列化相關(guān),通過(guò)分析情境出現(xiàn)的原因,使讀者輕松牢記 Java 序列化中的一些高級(jí)認(rèn)識(shí)。


--------------------------------------------------------------------------------
回頁(yè)首
文章結(jié)構(gòu)

本文將逐一的介紹幾個(gè)情境,順序如下面的列表。

•序列化 ID 的問(wèn)題
•靜態(tài)變量序列化
•父類(lèi)的序列化與 Transient 關(guān)鍵字
•對(duì)敏感字段加密
•序列化存儲(chǔ)規(guī)則
列表的每一部分講述了一個(gè)單獨(dú)的情境,讀者可以分別查看。


--------------------------------------------------------------------------------
回頁(yè)首
序列化 ID 問(wèn)題

情境:兩個(gè)客戶(hù)端 A 和 B 試圖通過(guò)網(wǎng)絡(luò)傳遞對(duì)象數(shù)據(jù),A 端將對(duì)象 C 序列化為二進(jìn)制數(shù)據(jù)再傳給 B,B 反序列化得到 C。

問(wèn)題:C 對(duì)象的全類(lèi)路徑假設(shè)為 com.inout.Test,在 A 和 B 端都有這么一個(gè)類(lèi)文件,功能代碼完全一致。也都實(shí)現(xiàn)了 Serializable 接口,但是反序列化時(shí)總是提示不成功。

解決:虛擬機(jī)是否允許反序列化,不僅取決于類(lèi)路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類(lèi)的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清單 1 中,雖然兩個(gè)類(lèi)的功能代碼完全一致,但是序列化 ID 不同,他們無(wú)法相互序列化和反序列化。


清單 1. 相同功能代碼不同序列化 ID 的類(lèi)對(duì)比
  1. package com.inout;

  2. import java.io.Serializable;

  3. public class A implements Serializable {

  4. private static final long serialVersionUID = 1L;

  5. private String name;

  6. public String getName()
  7. {
  8. return name;
  9. }

  10. public void setName(String name)
  11. {
  12. this.name = name;
  13. }
  14. }

  15. package com.inout;

  16. import java.io.Serializable;

  17. public class A implements Serializable {

  18. private static final long serialVersionUID = 2L;

  19. private String name;

  20. public String getName()
  21. {
  22. return name;
  23. }

  24. public void setName(String name)
  25. {
  26. this.name = name;
  27. }
  28. }
復(fù)制代碼
序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類(lèi)型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒(méi)有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過(guò)改變序列化 ID 可以用來(lái)限制某些用戶(hù)的使用。

特性使用案例

讀者應(yīng)該聽(tīng)過(guò) Façade 模式,它是為應(yīng)用程序提供統(tǒng)一的訪問(wèn)接口,案例程序中的 Client 客戶(hù)端使用了該模式,案例程序結(jié)構(gòu)圖如圖 1 所示。


圖 1. 案例程序結(jié)構(gòu)


Client 端通過(guò) Façade Object 才可以與業(yè)務(wù)邏輯對(duì)象進(jìn)行交互。而客戶(hù)端的 Façade Object 不能直接由 Client 生成,而是需要 Server 端生成,然后序列化后通過(guò)網(wǎng)絡(luò)將二進(jìn)制對(duì)象數(shù)據(jù)傳給 Client,Client 負(fù)責(zé)反序列化得到 Façade 對(duì)象。該模式可以使得 Client 端程序的使用需要服務(wù)器端的許可,同時(shí) Client 端和服務(wù)器端的 Façade Object 類(lèi)需要保持一致。當(dāng)服務(wù)器端想要進(jìn)行版本更新時(shí),只要將服務(wù)器端的 Façade Object 類(lèi)的序列化 ID 再次生成,當(dāng) Client 端反序列化 Façade Object 就會(huì)失敗,也就是強(qiáng)制 Client 端從服務(wù)器端獲取最新程序。


--------------------------------------------------------------------------------
回頁(yè)首
靜態(tài)變量序列化

情境:查看清單 2 的代碼。


清單 2. 靜態(tài)變量序列化問(wèn)題代碼
  1. public class Test implements Serializable {

  2. private static final long serialVersionUID = 1L;

  3. public static int staticVar = 5;

  4. public static void main(String[] args) {
  5. try {
  6. //初始時(shí)staticVar為5
  7. ObjectOutputStream out = new ObjectOutputStream(
  8. new FileOutputStream("result.obj"));
  9. out.writeObject(new Test());
  10. out.close();

  11. //序列化后修改為10
  12. Test.staticVar = 10;

  13. ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
  14. "result.obj"));
  15. Test t = (Test) oin.readObject();
  16. oin.close();

  17. //再讀取,通過(guò)t.staticVar打印新的值
  18. System.out.println(t.staticVar);

  19. } catch (FileNotFoundException e) {
  20. e.printStackTrace();
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. } catch (ClassNotFoundException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
復(fù)制代碼
清單 2 中的 main 方法,將對(duì)象序列化后,修改靜態(tài)變量的數(shù)值,再將序列化對(duì)象讀取出來(lái),然后通過(guò)讀取出來(lái)的對(duì)象獲得靜態(tài)變量的數(shù)值并打印出來(lái)。依照清單 2,這個(gè) System.out.println(t.staticVar) 語(yǔ)句輸出的是 10 還是 5 呢?

最后的輸出是 10,對(duì)于無(wú)法理解的讀者認(rèn)為,打印的 staticVar 是從讀取的對(duì)象里獲得的,應(yīng)該是保存時(shí)的狀態(tài)才對(duì)。之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類(lèi)的狀態(tài),因此 序列化并不保存靜態(tài)變量。


--------------------------------------------------------------------------------
回頁(yè)首
父類(lèi)的序列化與 Transient 關(guān)鍵字

情境:一個(gè)子類(lèi)實(shí)現(xiàn)了 Serializable 接口,它的父類(lèi)都沒(méi)有實(shí)現(xiàn) Serializable 接口,序列化該子類(lèi)對(duì)象,然后反序列化后輸出父類(lèi)定義的某變量的數(shù)值,該變量數(shù)值與序列化時(shí)的數(shù)值不同。

解決:要想將父類(lèi)對(duì)象也序列化,就需要讓父類(lèi)也實(shí)現(xiàn)Serializable 接口。如果父類(lèi)不實(shí)現(xiàn)的話(huà)的,就 需要有默認(rèn)的無(wú)參的構(gòu)造函數(shù)。在父類(lèi)沒(méi)有實(shí)現(xiàn) Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè) Java 對(duì)象的構(gòu)造必須先有父對(duì)象,才有子對(duì)象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對(duì)象,只能調(diào)用父類(lèi)的無(wú)參構(gòu)造函數(shù)作為默認(rèn)的父對(duì)象。因此當(dāng)我們?nèi)「笇?duì)象的變量值時(shí),它的值是調(diào)用父類(lèi)無(wú)參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況,在父類(lèi)無(wú)參構(gòu)造函數(shù)中對(duì)變量進(jìn)行初始化,否則的話(huà),父類(lèi)變量值都是默認(rèn)聲明的值,如 int 型的默認(rèn)是 0,string 型的默認(rèn)是 null。

Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null。

特性使用案例

我們熟悉使用 Transient 關(guān)鍵字可以使得字段不被序列化,那么還有別的方法嗎?根據(jù)父類(lèi)對(duì)象序列化的規(guī)則,我們可以將不需要被序列化的字段抽取出來(lái)放到父類(lèi)中,子類(lèi)實(shí)現(xiàn) Serializable 接口,父類(lèi)不實(shí)現(xiàn),根據(jù)父類(lèi)序列化規(guī)則,父類(lèi)的字段數(shù)據(jù)將不被序列化,形成類(lèi)圖如圖 2 所示。


圖 2. 案例程序類(lèi)圖


上圖中可以看出,attr1、attr2、attr3、attr5 都不會(huì)被序列化,放在父類(lèi)中的好處在于當(dāng)有另外一個(gè) Child 類(lèi)時(shí),attr1、attr2、attr3 依然不會(huì)被序列化,不用重復(fù)抒寫(xiě) transient,代碼簡(jiǎn)潔。


--------------------------------------------------------------------------------
回頁(yè)首
對(duì)敏感字段加密

情境:服務(wù)器端給客戶(hù)端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶(hù)端如果擁有解密的密鑰,只有在客戶(hù)端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。

解決:在序列化過(guò)程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類(lèi)里的 writeObject 和 readObject 方法,進(jìn)行用戶(hù)自定義的序列化和反序列化,如果沒(méi)有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶(hù)自定義的 writeObject 和 readObject 方法可以允許用戶(hù)控制序列化的過(guò)程,比如可以在序列化的過(guò)程中動(dòng)態(tài)改變序列化的數(shù)值。基于這個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作,清單 3 展示了這個(gè)過(guò)程。


清單 3. 靜態(tài)變量序列化問(wèn)題代碼
  1. private static final long serialVersionUID = 1L;

  2. private String password = "pass";

  3. public String getPassword() {
  4. return password;
  5. }

  6. public void setPassword(String password) {
  7. this.password = password;
  8. }

  9. private void writeObject(ObjectOutputStream out) {
  10. try {
  11. PutField putFields = out.putFields();
  12. System.out.println("原密碼:" + password);
  13. password = "encryption";//模擬加密
  14. putFields.put("password", password);
  15. System.out.println("加密后的密碼" + password);
  16. out.writeFields();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }

  21. private void readObject(ObjectInputStream in) {
  22. try {
  23. GetField readFields = in.readFields();
  24. Object object = readFields.get("password", "");
  25. System.out.println("要解密的字符串:" + object.toString());
  26. password = "pass";//模擬解密,需要獲得本地的密鑰
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. } catch (ClassNotFoundException e) {
  30. e.printStackTrace();
  31. }

  32. }

  33. public static void main(String[] args) {
  34. try {
  35. ObjectOutputStream out = new ObjectOutputStream(
  36. new FileOutputStream("result.obj"));
  37. out.writeObject(new Test());
  38. out.close();

  39. ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
  40. "result.obj"));
  41. Test t = (Test) oin.readObject();
  42. System.out.println("解密后的字符串:" + t.getPassword());
  43. oin.close();
  44. } catch (FileNotFoundException e) {
  45. e.printStackTrace();
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. } catch (ClassNotFoundException e) {
  49. e.printStackTrace();
  50. }
  51. }
復(fù)制代碼
在清單 3 的 writeObject 方法中,對(duì)密碼進(jìn)行了加密,在 readObject 中則對(duì) password 進(jìn)行解密,只有擁有密鑰的客戶(hù)端,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全。執(zhí)行清單 3 后控制臺(tái)輸出如圖 3 所示。


圖 3. 數(shù)據(jù)加密演示


特性使用案例

RMI 技術(shù)是完全基于 Java 序列化技術(shù)的,服務(wù)器端接口調(diào)用所需要的參數(shù)對(duì)象來(lái)至于客戶(hù)端,它們通過(guò)網(wǎng)絡(luò)相互傳輸。這就涉及 RMI 的安全傳輸?shù)膯?wèn)題。一些敏感的字段,如用戶(hù)名密碼(用戶(hù)登錄時(shí)需要對(duì)密碼進(jìn)行傳輸),我們希望對(duì)其進(jìn)行加密,這時(shí),就可以采用本節(jié)介紹的方法在客戶(hù)端對(duì)密碼進(jìn)行加密,服務(wù)器端進(jìn)行解密,確保數(shù)據(jù)傳輸?shù)陌踩浴?


--------------------------------------------------------------------------------
回頁(yè)首
序列化存儲(chǔ)規(guī)則

情境:?jiǎn)栴}代碼如清單 4 所示。


清單 4. 存儲(chǔ)規(guī)則問(wèn)題代碼
  1. ObjectOutputStream out = new ObjectOutputStream(
  2. new FileOutputStream("result.obj"));
  3. Test test = new Test();
  4. //試圖將對(duì)象兩次寫(xiě)入文件
  5. out.writeObject(test);
  6. out.flush();
  7. System.out.println(new File("result.obj").length());
  8. out.writeObject(test);
  9. out.close();
  10. System.out.println(new File("result.obj").length());

  11. ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
  12. "result.obj"));
  13. //從文件依次讀出兩個(gè)文件
  14. Test t1 = (Test) oin.readObject();
  15. Test t2 = (Test) oin.readObject();
  16. oin.close();

  17. //判斷兩個(gè)引用是否指向同一個(gè)對(duì)象
  18. System.out.println(t1 == t2);
復(fù)制代碼
清單 3 中對(duì)同一對(duì)象兩次寫(xiě)入文件,打印出寫(xiě)入一次對(duì)象后的存儲(chǔ)大小和寫(xiě)入兩次后的存儲(chǔ)大小,然后從文件中反序列化出兩個(gè)對(duì)象,比較這兩個(gè)對(duì)象是否為同一對(duì)象。一般的思維是,兩次寫(xiě)入對(duì)象,文件大小會(huì)變?yōu)閮杀兜拇笮,反序列化時(shí),由于從文件讀取,生成了兩個(gè)對(duì)象,判斷相等時(shí)應(yīng)該是輸入 false 才對(duì),但是最后結(jié)果輸出如圖 4 所示。


圖 4. 示例程序輸出


我們看到,第二次寫(xiě)入對(duì)象時(shí)文件只增加了 5 字節(jié),并且兩個(gè)對(duì)象是相等的,這是為什么呢?

解答:Java 序列化機(jī)制為了節(jié)省磁盤(pán)空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫(xiě)入文件的為同一對(duì)象時(shí),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用,上面增加的 5 字節(jié)的存儲(chǔ)空間就是新增引用和一些控制信息的空間。反序列化時(shí),恢復(fù)引用關(guān)系,使得清單 3 中的 t1 和 t2 指向唯一的對(duì)象,二者相等,輸出 true。該存儲(chǔ)規(guī)則極大的節(jié)省了存儲(chǔ)空間。

特性案例分析

查看清單 5 的代碼。


清單 5. 案例代碼
  1. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
  2. Test test = new Test();
  3. test.i = 1;
  4. out.writeObject(test);
  5. out.flush();
  6. test.i = 2;
  7. out.writeObject(test);
  8. out.close();
  9. ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
  10. "result.obj"));
  11. Test t1 = (Test) oin.readObject();
  12. Test t2 = (Test) oin.readObject();
  13. System.out.println(t1.i);
  14. System.out.println(t2.i);
復(fù)制代碼
清單 4 的目的是希望將 test 對(duì)象兩次保存到 result.obj 文件中,寫(xiě)入一次以后修改對(duì)象屬性值再次保存第二次,然后從 result.obj 中再依次讀出兩個(gè)對(duì)象,輸出這兩個(gè)對(duì)象的 i 屬性值。案例代碼的目的原本是希望一次性傳輸對(duì)象修改前后的狀態(tài)。

結(jié)果兩個(gè)輸出的都是 1, 原因就是第一次寫(xiě)入對(duì)象以后,第二次再試圖寫(xiě)的時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫(xiě)入文件,因此只保存第二次寫(xiě)的引用,所以讀取時(shí),都是第一次保存的對(duì)象。讀者在使用一個(gè)文件多次 writeObject 需要特別注意這個(gè)問(wèn)題。


論壇徽章:
0
2 [報(bào)告]
發(fā)表于 2012-03-19 17:33 |只看該作者
謝謝分享
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專(zhuān)區(qū)
中國(guó)互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過(guò)ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP