Java序列化的常見問題有哪些

這篇文章主要講解了“Java序列化的常見問題有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Java序列化的常見問題有哪些”吧!

公司主營業(yè)務(wù):成都做網(wǎng)站、成都網(wǎng)站建設(shè)、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚、勤奮敬業(yè)、活力青春激揚、勤奮敬業(yè)、活力澎湃、和諧高效的團隊。公司秉承以“開放、自由、嚴(yán)謹、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。成都創(chuàng)新互聯(lián)公司推出西安免費做網(wǎng)站回饋大家。

 Java序列化的常見問題有哪些

前幾天看到一個2016年挺有趣的一個故障復(fù)盤,有一哥們給底層的HSF服務(wù)返回值加了一個字段,秉承著“加字段一定是安全的”這種慣性思維就直接上線了,上線后發(fā)現(xiàn)這個接口成功率直接跌0,下游的服務(wù)拋出類似下面這個異常堆棧

java.io.InvalidClassException:com.taobao.query.TestSerializable;   local class incompatible: stream classdesc serialVersionUID = -7165097063094245447,local class    serialVersionUID = 6678378625230229450

看到這個堆??赡苡欣纤緳C已經(jīng)反應(yīng)過來了,下面我們就看下這種異常到底是如何發(fā)生的

Java序列化與反序列化

  •  序列化:將對象寫入到IO流中

  •  反序列化:從IO流中恢復(fù)對象

序列化機制允許將實現(xiàn)序列化的Java對象轉(zhuǎn)換為字節(jié)序列,這些字節(jié)序列可以保存在磁盤上,或通過網(wǎng)絡(luò)傳輸,以達到以后恢復(fù)成原來的對象。序列化機制使得對象可以脫離程序的運行而獨立存在。

要想有序列化的能力,得實現(xiàn)Serializable接口,就像下面的這個例子一樣:

public class SerializableTest implements Serializable {      private static final long serialVersionUID = -3751255153289772365L;  }

這里面一個關(guān)鍵的點是serialVersionUID,JVM會在運行時判斷類的serialVersionUID來驗證版本一致性,如果傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)類的serialVersionUID相同則認為是一致的,可以進行反序列化,否則就會出現(xiàn)序列化版本不一致的異常。

在上面的例子中,我們通過IDEA的插件已經(jīng)自動為SerializableTest生成了一個serialVersionUID,如果我們不指定serialVersionUID,編譯器在編譯的時候也會根據(jù)類名、接口名、成員方法及屬性等來生成一個64位的哈希字段 。

Dubbo與序列化

Java序列化的常見問題有哪些

/dev-guide/images/dubbo-extension.jpg

圖片來源:https://dubbo.apache.org/zh/docs/v2.7/dev/design/

從Dubbo的調(diào)用鏈可以發(fā)現(xiàn)是有一個序列化節(jié)點的,其支持的序列化協(xié)議一共有四種:

  1.  dubbo序列化:阿里尚未開發(fā)成熟的高效java序列化實現(xiàn),阿里不建議在生產(chǎn)環(huán)境使用它

  2.  hessian2序列化:hessian是一種跨語言的高效二進制序列化方式。但這里實際不是原生的hessian2序列化,而是阿里修改過的hessian lite,它是dubbo RPC默認啟用的序列化方式

  3.  json序列化:目前有兩種實現(xiàn),一種是采用的阿里的fastjson庫,另一種是采用dubbo中自己實現(xiàn)的簡單json庫,但其實現(xiàn)都不是特別成熟,而且json這種文本序列化性能一般不如上面兩種二進制序列化。

  4.  java序列化:主要是采用JDK自帶的Java序列化實現(xiàn),性能很不理想。

從那個帖子看當(dāng)時HSF服務(wù)提供集群設(shè)置的序列化方式是java序列化,而不是像現(xiàn)在一樣默認hessian2,如果在RPC中使用了Java序列化,那下面的這三個坑一定注意不要踩

類實現(xiàn)了Serializable接口,但是卻沒有指定serialVersionUID

我們之前在文中提過,如果實現(xiàn)了Serializable的類沒有指定serialVersionUID,編譯器編譯的時候會根據(jù)類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,這就決定了這個類在序列化上一定不是向前兼容的,前文中的那個故障就是踩了這個坑。我們在本地模擬一下這個case:

假如我們先有Student這樣的一個類

public class Student implements Serializable {      private static int startId = 1000;      private int id;      public Student() {          id = startId ++;      }  }

我們將其序列化到磁盤:

private static void serialize() {      try {          Student student = new Student();          FileOutputStream fileOut =                  new FileOutputStream("/tmp/student.ser");          ObjectOutputStream out = new ObjectOutputStream(fileOut);          out.writeObject(student);          out.close();          fileOut.close();          System.out.printf("Serialized data is saved in /tmp/student.ser");      } catch (              IOException i) {          i.printStackTrace();      }  }

然后給Student類加一個字段

public class Student implements Serializable {      private static int startId = 1000;      private int id;    // 注意這里我們已經(jīng)加了一個屬性      private String name;      public Student() {          id = startId ++;      }  }

我們再去解碼,發(fā)現(xiàn)程序會拋出異常:

java.io.InvalidClassException: com.idealism.base.Student; local class incompatible: stream classdesc serialVersionUID = -1534228028811562580, local class serialVersionUID = 630353564791955009  at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)  at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2001)   at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848)   at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158)   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)   at com.idealism.base.SerializableTest.deserialize(SerializableTest.java:34)   at com.idealism.base.SerializableTest.main(SerializableTest.java:9)

其實到這里我們就完整的模擬了前文中的那個故障,其根因是RPC的參數(shù)實現(xiàn)了Serializable接口,但是沒有指定serialVersionUID,編譯器會根據(jù)類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,當(dāng)服務(wù)端類升級之后導(dǎo)致了服務(wù)端發(fā)送給客戶端的字節(jié)流中的serialVersionUID發(fā)生了改變,因此當(dāng)客戶端反序列化去檢查serialVersionUID字段的時候發(fā)現(xiàn)發(fā)生了變化被判定了異常。

父類實現(xiàn)了Serializable接口,并且指定了serialVersionUID但是子類沒有指定serialVersionUID

我們對前面的例子中的Student類稍微改一下

public class Student extends Base{      private static int startId = 1000;      private int id;      public Student() {          id = startId ++;      }  }

其中父類長這樣:

public class Base implements Serializable {      private static final long serialVersionUID = 218886242758597651L;      private Date gmtCreate;  }

如果我們按照之前的討論在本地進行一次序列化和反序列化,程序依然拋異常:

java.io.InvalidClassException: com.idealism.base.Student; local class incompatible: stream classdesc serialVersionUID = 1049562984784675762, local class serialVersionUID = 7566357243685852874  at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)   at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2001)   at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848)   at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158)   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)   at com.idealism.base.SerializableTest.deserialize(SerializableTest.java:34)   at com.idealism.base.SerializableTest.main(SerializableTest.java:9)

我們在設(shè)計類的時候公共屬性要放到基類,這條經(jīng)驗指導(dǎo)放到這個case中仍然不太正確,而且這個case比上一個還要隱蔽,問題出主要是通過IDEA插件生成的serialVersionUID的修飾符是pivate導(dǎo)致這個字段在子類中不可見,子類中的serialVersionUID仍然是編譯器自動生成的。當(dāng)然可以把父類中serialVersionUID的改為非private來解這個問題,不過我仍然建議每個有序列化需求的類都顯式指定serialVersionUID的值。

如果序列化遇到類之間的組合或者繼承關(guān)系,則Java按照下面的規(guī)則處理:

  •  當(dāng)一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化,而不管其是否實現(xiàn)了Serializable接口

  •  如果子類實現(xiàn)了Serializable,則序列化時只序列化子類,不會序列化父類中的屬性

  •  如果父類實現(xiàn)了Serializable,則序列化時子類和父類都會被序列化,異常場景如本例所指

還有一點要注意:如果類的實例中有靜態(tài)變量,改屬性不會被序列化和反序列化

類中有枚舉值

《阿里巴巴開發(fā)規(guī)約》中有這么一條:

【強制】二方庫例可以定義枚舉類型,參數(shù)可以使用枚舉類型,但是接口返回值不允許使用枚舉類型或者包含枚舉類型的POJO對象。

說明:由于升級原因,導(dǎo)致雙方的枚舉類不盡相同,在接口解析,類反序列化時出現(xiàn)異常

這里會出現(xiàn)這樣一個限制的原因是Java對枚舉的序列化和反序列化采用完全不同的策略。序列化的結(jié)果中僅包含枚舉的名字,而不包含枚舉的具體定義,反序列化的時候客戶端從序列化結(jié)果中讀取枚舉的name,然后調(diào)用java.lang.Enum#valueOf根據(jù)本地的枚舉定義獲取具體的枚舉值。

我們?nèi)匀挥弥暗拇a舉例:

public class Student implements Serializable {      private static final long serialVersionUID = 2528736437985230667L;          private static int startId = 1000;      private int id;      private String name;      // 新增字段,校服尺碼,其類型是一個枚舉      private SchoolUniformSizeEnum schoolUniformSize;      public Student() {          id = startId ++;      }  }

假如學(xué)生這個類中新增了一個校服尺碼的枚舉值

public enum SchoolUniformSizeEnum {      SMALL,      MEDIUM,      LARGE  }

假如服務(wù)端此時對這個枚舉進行了升級,但是客戶端的二方包中仍然只有三個值:

public enum SchoolUniformSizeEnum {      SMALL,      MEDIUM,      LARGE,      OVERSIZED  }

如果服務(wù)端有邏輯給客戶端返回了這個新增的枚舉值:

private static void serialize() {      try {          Student student = new Student();          // 服務(wù)端升級了枚舉          student.setSchoolUniformSize(SchoolUniformSizeEnum.OVERSIZED);          FileOutputStream fileOut =                  new FileOutputStream("/tmp/student.ser");          ObjectOutputStream out = new ObjectOutputStream(fileOut);          out.writeObject(student);          out.close();          fileOut.close();          System.out.printf("Serialized data is saved in /tmp/student.ser");      } catch (              IOException i) {          i.printStackTrace();      }  }

因為客戶端的二方包還沒有升級,所以當(dāng)客戶端讀到這個新的字節(jié)流并序列化的時候會因為找不到對應(yīng)的枚舉值而拋異常。

java.io.InvalidObjectException: enum constant OVERSIZED does not exist in class com.idealism.base.SchoolUniformSizeEnum   at java.io.ObjectInputStream.readEnum(ObjectInputStream.java:2130)   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1659)   at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2403)   at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2327)   at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2185)   at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)   at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)   at com.idealism.base.SerializableTest.deserialize(SerializableTest.java:36)   at com.idealism.base.SerializableTest.main(SerializableTest.java:9)

2016年的故障還值得我們?nèi)?fù)盤嗎

看到這里可能有小伙伴覺得,我這輩子都不可能去修改Dubbo的序列化方式,就讓他hessian2到底吧,我不得不承認確實是這樣的。如果把序列化光限制在RPC這一個場景,未免有些狹隘。以阿里為例,其分布式緩存中間件Tair的寫接口可接受的入?yún)⒕褪且粋€Serializable,好在我們平常往緩存中塞東西都是以String為key的,但萬一有前人真的用了一個實現(xiàn)了Serializable的類,并且恰好沒有指定serialVersionUID,那新來的你不就正好踩坑了么。所以在遇到序列化的地方需要仔細查看有沒有踩文章中列出來的三個坑。

感謝各位的閱讀,以上就是“Java序列化的常見問題有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Java序列化的常見問題有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

網(wǎng)站欄目:Java序列化的常見問題有哪些
標(biāo)題路徑:http://bm7419.com/article10/iioggo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈定制網(wǎng)站、自適應(yīng)網(wǎng)站、品牌網(wǎng)站設(shè)計、軟件開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化