轉載請注明原文地址:https://www.cnblogs.com/ygj0930/p/10857597.html
一:什么是序列化與反序列化
序列化:對象序列化是指將Java對象(動態的狀態,如變量、函數)轉換為字節流的過程,可以將其保存到磁盤文件中或通過網絡發送到任何其他程序。
反序列化:從字節流重構出Java對象的過程。
序列化得到的字節流是與平台無關的,在一個平台上序列化的對象可以在不同的平台上反序列化。
二:序列化的作用
1)對象持久化
我們知道,對象隨着程序的運行而被創建,然后在不可達時被回收,生命周期是短暫的。但是如果我們想長久地把對象的內容保存起來怎么辦呢?把它轉化為字節序列保存在存儲介質上即可。那就需要序列化。
2)網絡傳輸對象
我們知道,兩個進程之間通信時,傳遞的音頻、視頻等信息是以二進制序列形式來傳輸的。那么,對象也可以嗎?可以,通過序列化把主機A進程上的對象序列化為二進制序列,傳輸到主機B上的進程從序列中重構出該對象。這在RMI中應用廣泛,RMI的結果可以是一個對象。
3)進程間傳遞對象
三:如何序列化
1、讓類對象可以被序列化
1)默認序列化方式
定義類時實現Serializable接口即可,這個Serializable接口是一個空接口,沒有需要實現的方法。
作用是標記該類的對象可以被序列化,啟用其序列化功能。通過調用 ObjectOutputStream和ObjectInputStream的方法來即可對該對象進行序列化和反序列化。
2)實現Serializable接口
定義類時,實現Serializable接口,並在類中重寫兩個序列化與反序列化接口,在其中定義對象序列化與反序列化動作。
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
這兩個方法,在方法中通過對象輸入流參數、對象輸出流參數進行自定義的內容輸出。
這樣通過 ObjectOutputStream和ObjectInputStream 序列化和反序列化對象時會自動調用類中定義的writeObject、和readObject方法而不是默認的序列化和反序列化方法。
3)實現Externalnalizable接口
在類中實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,在方法中定義類對象自定義的序列化和反序列化操作。
這樣通過對象輸出流和對象輸入流的輸入輸出方法序列化和反序列化對象時會自動調用類中定義的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。
2、序列化與反序列化API與使用過程
1)定義一個類,實現Serializable接口或者Externalizable接口,實現相應的序列化和反序列化方法(也可采取默認方法);
2)在程序代碼中創建對象后,創建對象輸出流ObjectOutputStream對象並在構造參數中指定流的輸出目標(比如一個文件),通過objectOutputStream.writeObject(obj)把對象序列化並輸出到流目標處;
3)在需要提取對象處:創建對象輸入流ObjectInputStream對象並在構造參數中指定流的來源,然后通過readObject()方法獲取對象,並通過強制類型轉換賦值給類對象引用。
四:序列化底層原理
序列化算法會按步驟執行以下事情:
1)當前類描述的元數據輸出為字節序列;【類定義描述、類中屬性定義描述】
2)超類描述輸出為字節序列;【如果超類還有超類,則依次遞歸,直至沒有超類】
3)從最頂層超類往下,依次輸出各類屬性值描述,直至當前類對象屬性值。
即:從下到上描述類定義,從上往下輸出屬性值。
五:序列化的特殊情況
1)靜態變量和transient關鍵字修飾的變量不能被序列化;
2)反序列化時要按照序列化的順序重構對象:如先序列化A后序列化B,則反序列化時也要先獲取A后獲取B,否則報錯。
3)serialVersionUID(序列化ID)的作用:決定着是否能夠成功反序列化。
虛擬機是否允許對象反序列化,不是取決於該對象所屬類路徑和功能代碼是否與虛擬機加載的類一致,而是主要取決於對象所屬類與虛擬機加載的該類的序列化 ID 是否一致。
java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地實體類中的serialVersionUID進行比較,如果相同則認為是一致的,便可以進行反序列化,否則就會報序列化版本不一致的異常。
4)自定義序列化方法的應用場景:對某些敏感數據進行加密操作后再序列化;反序列化對加密數據進行解密操作。
5)重復序列化:同一個對象重復序列化時,不會把對象內容再次序列化,而是新增一個引用指向第一次序列化時的對象而已。
六:序列化破壞單例模式
見另一篇博文:https://www.cnblogs.com/ygj0930/p/10845530.html
七:為什么說反序列化並不安全——序列化漏洞[反序列化攻擊]
反序列化是一系列安全問題的根源:攻擊者能夠將惡意數據序列化並存儲到數據庫或內存中,當應用進行反序列化時,應用會執行到惡意代碼。
在谷歌內部,這個缺陷被稱為“瘋狂的小部件 (Mad Gadget)”,外界對它的叫法是 “Java 啟示錄 (Apocalypse)”。
怎樣規避序列化漏洞:
1)對序列化對象執行完整性檢查或加密,以防止惡意對象創建或數據篡改;最常見的例子之一就是JWT:JWT由3部分組成:Header,Payload,Verify Signature,最后的簽名部分其實就是對數據進行完整性校驗的關鍵部分,用secret對數據部分進行哈希計算,隨后檢查計算出來的哈希值是否和請求中的JWT簽名部分的哈希值相同。若兩者一致則認為數據完整性沒有被破壞,若兩者有差異則說明數據被修改過。
2)在創建對象之前強制執行嚴格的類型約束;
3)隔離反序列化的代碼,使其在非常低的特權環境中運行;
4)記錄反序列化的例外情況和失敗信息,如:傳入的類型不是預期的類型,或者反序列處理引發的例外情況;
5)限制或監視來自於容器或服務器傳入和傳出的反序列化網絡連接;
6)監視反序列化,當用戶持續進行反序列化時,對用戶進行警告。
八:protobuf
protobuf:谷歌公司出的一款開源項目,序列號性能好,效率高,並且支持多種語言,例如:java,C++,python等。
protobuf3語言語法:https://blog.csdn.net/u011518120/article/details/54604615
protobuf3簡單使用:https://blog.csdn.net/fangxiaoji/article/details/78826165
九:其他序列化方式對比
目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。
-
JSON:用途最廣泛,序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。
優點:使用方便。
缺點:數據冗長,轉碼性能一般。 -
XML:很久之前的轉碼方法,現在用的不多。
優點:暫時沒發現。
缺點:數據冗長,轉碼性能一般。 -
Serialzable:JDK自帶的序列化。
優點:使用方便。
缺點:無法跨語言、序列化的碼流太大 、轉碼性能低下、存在安全漏洞。 -
hessian:基於 binary-RPC實現的遠程通訊library,使用二進制傳輸數據。
優點:數據長度小。
缺點:性能低下。 - kryo:快速、高效的序列化框架。只能在java中使用,和前端非java語言的通訊存在極大的隔閡。
- protobuf:谷歌公司出的一款開源項目,轉碼性能高,支持多語言。