一文講透Java序列化

 

本文目錄java

  • 1、序列化是什麼
  • 2、爲何須要序列化
  • 3、序列化怎麼用
  • 4、序列化深度探祕
    • 4.1 爲何必須實現Serializable接口
    • 4.2 被序列化對象的字段是引用時該怎麼辦 
    • 4.3 同一個對象會被序列化屢次嗎
    • 4.4 只想序列化對象的部分字段該怎麼辦
    • 4.5 被序列化對象具備繼承關係該怎麼辦
  • 5、serialVersionUID的做用及自動生成
  • 6、序列化的缺點
  • 7、參考文獻

 

前言

 

Oracle 公司計劃廢除 Java 中的古董:序列化技術,由於它帶來了許多嚴重的安全問題(如序列化存儲安全、反序列化安全、傳輸安全等),據統計,至少有3分之1的漏洞是序列化帶來的,這也是 1997 年誕生序列化技術的一個巨大錯誤。可是,序列化技術如今在 Java 應用中無處不在,特別是如今的持久化框架和分佈式技術中,都須要利用序列化來傳輸對象,如:Hibernate、Mybatis、Java RMI、Dubbo等,即對象要存儲或者傳輸都不可避免要用到序列化技術,因此刪除序列化技術將是一個長期的計劃。程序員

 

你在實際工做中可能會很難有機會真正用到Java自帶的序列化技術了,工業界通常也會選擇一些更安全的對象編解碼方案例如Google的Protobuf等。因此,對於Java序列化,咱們沒必要再投入過多的精力學習,你花20分鐘讀完本文所掌握的知識,對於應付平常源碼閱讀中遇到的遺留的Java序列化技術應該是足夠了。算法

 

1、序列化是什麼

 

序列化機制容許將實現序列化的Java對象轉換成字節序列,這些字節序列能夠保存在磁盤上,或經過網絡傳輸,以備之後從新恢復成原來的對象。序列化機制使得對象能夠脫離程序的運行而獨立存在。編程

  • 序列化:將一個Java對象寫入IO流中
  • 反序列化:從IO流中恢復該Java對象

 

本文中用序列化來簡稱整個序列化和反序列化機制。 瀏覽器

 

2、爲何須要序列化

 

全部可能在網絡上傳輸的對象的類都應該是可序列化的,不然程序將會出現異常,好比RMI(Remote Method Invoke,即遠程方法調用,是JavaEE的基礎)過程當中的參數和返回值;全部須要保存到磁盤裏的對象的類都必須可序列化,好比Web應用中須要保存到HttpSession或ServletContext屬性的Java對象。安全

 

由於序列化是RMI過程的參數和返回值都必須實現的機制,而RMI又是Java EE技術的基礎——全部的分佈式應用經常須要跨平臺、跨網絡,因此要求全部傳遞的參數、返回值必須實現序列化。所以序列化機制是Java EE平臺的基礎。一般建議:程序建立的每一個JavaBean類都實現Serializable。服務器

 

3、序列化怎麼用

 

若是一個類的對象須要序列化,那麼在Java語法層面,這個類須要:網絡

  • 實現Serializable接口
  • 使用ObjectOutputStream將對象輸出到流,實現對象的序列化;使用ObjectInputStream從流中讀取對象,實現對象的反序列化

 

下面咱們經過代碼示例來看看序列化最基本的用法。咱們建立了Person類,其擁有兩個基本類型的屬性,並實現了Serializable接口。testSerialize方法用來測試序列化,testDeserialize方法用來測試反序列化。框架

 1 import org.junit.Test;
 2 
 3 import java.io.*;
 4 
 5 public class SerializableTest {
 6 
 7     @Test
 8     public void testSerialize() {
 9         Person one = new Person(12, 148.2);
10         Person two = new Person(35, 177.8);
11 
12         try (ObjectOutputStream output =
13                      new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
14             output.writeObject(one);
15             output.writeObject(two);
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19     }
20 
21     @Test
22     public void testDeserialize() {
23 
24         try (ObjectInputStream input =
25                      new ObjectInputStream(new FileInputStream("Person.txt"))) {
26             Person one = (Person) input.readObject();
27             Person two = (Person) input.readObject();
28 
29             System.out.println(one);
30             System.out.println(two);
31         } catch (IOException e) {
32             e.printStackTrace();
33         } catch (ClassNotFoundException e) {
34             e.printStackTrace();
35         }
36     }
37 }
38 
39 class Person implements Serializable {
40     int age;
41     double height;
42 
43     public Person(int age, double height) {
44         this.age = age;
45         this.height = height;
46     }
47 
48     @Override
49     public String toString() {
50         return "Person{" +
51                 "age=" + age +
52                 ", height=" + height +
53                 '}';
54     }
55 }

 

4、序列化深度探祕

4.1 爲何必須實現Serializable接口

若是某個類須要支持序列化功能,那麼它必須實現Serializable接口,不然會報 java.io.NotSerializableException。Serializable接口是一個標誌性接口(Marker Interface),也就是說,該接口並不包含任何具體的方法,是一個空接口,僅僅用來判斷該類是否可以序列化。JDK8中Serializable接口的源碼以下:dom

1 package java.io;
2 
3 public interface Serializable {
4 }

 

在 ObjectOutputStream.java 的 writeObject0 方法中,咱們確實能夠看到對對象是否實現了 Serializable接口進行了驗證(第15行),不然會拋出 NotSerializableException 異常(第22行)。

 1     private void writeObject0(Object obj, boolean unshared)
 2         throws IOException
 3     {
 4         boolean oldMode = bout.setBlockDataMode(false);
 5         depth++;
 6         try {
 7             ...
 8             // remaining cases
 9             if (obj instanceof String) {
10                 writeString((String) obj, unshared);
11             } else if (cl.isArray()) {
12                 writeArray(obj, desc, unshared);
13             } else if (obj instanceof Enum) {
14                 writeEnum((Enum<?>) obj, desc, unshared);
15             } else if (obj instanceof Serializable) {
16                 writeOrdinaryObject(obj, desc, unshared);
17             } else {
18                 if (extendedDebugInfo) {
19                     throw new NotSerializableException(
20                         cl.getName() + "\n" + debugInfoStack.toString());
21                 } else {
22                     throw new NotSerializableException(cl.getName());
23                 }
24             }
25         } finally {
26             depth--;
27             bout.setBlockDataMode(oldMode);
28         }
29     }

 

4.2 被序列化對象的字段是引用時該怎麼辦

在第三部分「序列化怎麼用」部分的示例中,Person類的字段全都是基本類型,咱們知道基本類型其地址中直接存放的就是它的值,那若是是引用類型呢?引用類型其地址中存放的是指向堆內存中的一個地址,難道序列化時就是將這個地址進行了保存嗎?顯然,這是說不通的,由於對象的內存地址是可變的,在同一系統的不一樣運行時刻或者是不一樣系統中,對象的地址確定是不一樣的,所以,序列化內存地址沒有意義。

 

若是被序列化對象的字段是引用,那麼要求該引用的類型也是可序列化實現了Serializable接口的,不然沒法序列化。當對某個對象進行序列化時,系統會自動把該對象的全部Field依次進行序列化,若是某個Field引用到另外一個對象,則被引用的對象也會被序列化;若是被引用的對象的Field也引用了其餘對象,則被引用的對象也會被序列化,這種狀況被稱爲遞歸序列化。

 

4.3 同一個對象會被序列化屢次嗎

若是對象A和對象B同時引用了對象C,那麼,當序列化對象A和對象B時,對象C會被序列化兩次嗎?答案顯然是不會

 

要解釋這個問題,就不得不說一下Java序列化的基本算法了:

  • 全部序列化到二進制流的對象都有一個序列化編號
  • 當程序試圖序列化一個對象時,程序將先檢查該對象是否已經被序列化過,只有該對象從未(在本次虛擬機中)被序列化過,系統纔會將該對象轉換成字節序列並賦予一個惟一的編號
  • 若是某個對象已經序列化過,程序將只是直接輸出其序列化編號,而不是再次從新序列化該對象

 

4.4 只想序列化對象的部分字段該怎麼辦

 在一些特殊的場景下,若是一個類裏包含的某些Field值是敏感信息,例如銀行帳戶信息等,這時不但願系統將該Field值進行序列化;或者某個Field的類型是不可序列化的,所以不但願對該Field進行遞歸序列化,以免引起java.io.NotSerializableException異常。

 

此時,咱們就須要自定義序列化了。自定義序列化的經常使用方式有兩種:

  • 使用transient關鍵字
  • 重寫writeObject與readObject方法

 

咱們先看第一種方式,使用transient關鍵字。transient關鍵字只能用於修飾Field,不可修飾Java程序中的其餘成分。使用transient修飾的屬性,java序列化時,會忽略掉此字段,因此反序列化出的對象,被transient修飾的屬性是默認值。對於引用類型,值是null;基本類型,值是0;boolean類型,值是false。

 

下列代碼中,咱們把People的height字段設置爲transient,在反序列化時,可觀察到輸出爲默認值0.0。

 1 import org.junit.Test;
 2 
 3 import java.io.*;
 4 
 5 public class SerializableTest {
 6 
 7     @Test
 8     public void testSerialize() {
 9         Person one = new Person(12, 156.6);
10         Person two = new Person(16, 177.7);
11 
12         try (ObjectOutputStream output =
13                      new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
14             output.writeObject(one);
15             output.writeObject(two);
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19     }
20 
21     @Test
22     public void testDeserialize() {
23 
24         try (ObjectInputStream input =
25                      new ObjectInputStream(new FileInputStream("Person.txt"))) {
26             Person one = (Person) input.readObject();
27             Person two = (Person) input.readObject();
28 
29             System.out.println(one);
30             System.out.println(two);
31         } catch (IOException e) {
32             e.printStackTrace();
33         } catch (ClassNotFoundException e) {
34             e.printStackTrace();
35         }
36     }
37 }
38 
39 class Person implements Serializable{
40     protected int age;
41     protected transient double height;
42 
43     public Person() {
44     }
45 
46     public Person(int age, double height) {
47         this.age = age;
48         this.height = height;
49     }
50 
51     @Override
52     public String toString() {
53         return "Person{" +
54                 "age=" + age +
55                 ", height=" + height +
56                 '}';
57     }
58 }

 

 程序輸出:

Person{age=12, height=0.0}
Person{age=16, height=0.0}

Process finished with exit code 0

 

使用transient關鍵字修飾Field雖然簡單、方便,但被transient修飾的Field將被徹底隔離在序列化機制以外,這樣致使在反序列化恢復Java對象時沒法取得該Field值。Java還提供了一種自定義序列化機制,經過這種自定義序列化機制可讓程序控制如何序列化各Field,甚至徹底不序列化某些Field(與使用transient關鍵字的效果相同)。在序列化和反序列化過程當中須要特殊處理的類應該提供以下特殊簽名的方法,這些特殊的方法用以實現自定義序列化。

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 private void readObjectNoData()
     throws ObjectStreamException;

 

  • writeObject()方法負責寫入特定類的實例狀態,以便相應的readObject()方法能夠恢復它。經過重寫該方法,程序員能夠徹底得到對序列化機制的控制,能夠自主決定哪些Field須要序列化,須要怎樣序列化。在默認狀況下,該方法會調用out.defaultWriteObject來保存Java對象的各Field,從而能夠實現序列化Java對象狀態的目的。
  • readObject()方法負責從流中讀取並恢復對象Field,經過重寫該方法,程序員能夠徹底得到對反序列化機制的控制,能夠自主決定須要反序列化哪些Field,以及如何進行反序列化。在默認狀況下,該方法會調用in.defaultReadObject來恢復Java對象的非靜態和非瞬態Field。在一般狀況下,readObject()方法與writeObject()方法對應,若是writeObject()方法中對Java對象的Field進行了一些處理,則應該在readObject()方法中對其Field進行相應的反處理,以便正確恢復該對象。
  • 當序列化流不完整時,readObjectNoData()方法能夠用來正確地初始化反序列化的對象。例如,接收方使用的反序列化類的版本不一樣於發送方,或者接收方版本擴展的類不是發送方版本擴展的類,或者序列化流被篡改時,系統都會調用readObjectNoData()方法來初始化反序列化的對象。

下面的示例代碼中,咱們在writeObject方法中對Person的字段進行了簡單的加密處理,在readObject方法中對其進行了相應的解密。

 1 import org.junit.Test;
 2 
 3 import java.io.*;
 4 
 5 public class SerializableTest {
 6 
 7     @Test
 8     public void testSerialize() {
 9         Person one = new Person(12, 156.6);
10         Person two = new Person(16, 177.7);
11 
12         try (ObjectOutputStream output =
13                      new ObjectOutputStream(new FileOutputStream("Person.txt"))) {
14             output.writeObject(one);
15             output.writeObject(two);
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19     }
20 
21     @Test
22     public void testDeserialize() {
23 
24         try (ObjectInputStream input =
25                      new ObjectInputStream(new FileInputStream("Person.txt"))) {
26             Person one = (Person) input.readObject();
27             Person two = (Person) input.readObject();
28 
29             System.out.println(one);
30             System.out.println(two);
31         } catch (IOException e) {
32             e.printStackTrace();
33         } catch (ClassNotFoundException e) {
34             e.printStackTrace();
35         }
36     }
37 }
38 
39 class Person implements Serializable{
40     protected int age;
41     protected double height;
42 
43     public Person() {
44     }
45 
46     public Person(int age, double height) {
47         this.age = age;
48         this.height = height;
49     }
50 
51     private void writeObject(java.io.ObjectOutputStream out)
52             throws IOException {
53         System.out.println("Encryption!");
54         out.writeInt(age + 1);
55         out.writeDouble(height - 1);
56     }
57     private void readObject(java.io.ObjectInputStream in)
58             throws IOException, ClassNotFoundException {
59         System.out.println("Decryption!");
60         this.age = in.readInt() - 1;
61         this.height = in.readDouble() + 1;
62     }
63 
64     @Override
65     public String toString() {
66         return "Person{" +
67                 "age=" + age +
68                 ", height=" + height +
69                 '}';
70     }
71 }

 

4.5 被序列化對象具備繼承關係該怎麼辦

被序列化對象具備繼承關係時無非就兩種狀況,第一,該類具備子類,第二,該類具備父類。

 

當該類實現了Serializable接口且具備子類時,根據官方文檔中的說明,其子類自然具備可被序列化的屬性,不須要顯式實現Serializable接口;。

 All subtypes of a serializable class are themselves serializable. 

 

當該類實現了Serializable接口且具備父類時,,該類的父類須要實現Serializable接口嗎?在JDK8中Serializable接口的官方文檔中有這樣一段話:

 1 /**
 2  * ......
 3  *
 4  * To allow subtypes of non-serializable classes to be serialized, the
 5  * subtype may assume responsibility for saving and restoring the
 6  * state of the supertype's public, protected, and (if accessible)
 7  * package fields.  The subtype may assume this responsibility only if
 8  * the class it extends has an accessible no-arg constructor to
 9  * initialize the class's state.  It is an error to declare a class
10  * Serializable if this is not the case.  The error will be detected at
11  * runtime. 
12  *
13  * During deserialization, the fields of non-serializable classes will
14  * be initialized using the public or protected no-arg constructor of
15  * the class.  A no-arg constructor must be accessible to the subclass
16  * that is serializable.  The fields of serializable subclasses will
17  * be restored from the stream. 
18  */

 

閱讀文檔咱們得知,爲了使得不可序列化類的子類可以序列化,其子類必須擔負起保存和恢復其超類的public、protected 和 package(if accessible)實例域的責任,且要求其父類必須有一個可訪問的無參構造函數以使得在反序列化時可以初始化實例域。

 

咱們寫代碼驗證一下,若是父類中沒有可訪問的無參構造函數會發生什麼,注意Person類中沒有無參構造函數。

 1 import org.junit.Test;
 2 
 3 import java.io.*;
 4 
 5 public class SerializableTest {
 6 
 7     @Test
 8     public void testSerialize() {
 9         Student one = new Student(12, 156.6, "1234");
10         Student two = new Student(16, 177.7, "5678");
11 
12         try (ObjectOutputStream output =
13                      new ObjectOutputStream(new FileOutputStream("Student.txt"))) {
14             output.writeObject(one);
15             output.writeObject(two);
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19     }
20 
21     @Test
22     public void testDeserialize() {
23 
24         try (ObjectInputStream input =
25                      new ObjectInputStream(new FileInputStream("Student.txt"))) {
26             Student one = (Student) input.readObject();
27             Student two = (Student) input.readObject();
28 
29             System.out.println(one);
30             System.out.println(two);
31         } catch (IOException e) {
32             e.printStackTrace();
33         } catch (ClassNotFoundException e) {
34             e.printStackTrace();
35         }
36     }
37 }
38 
39 class Person{
40     protected int age;
41     protected double height;
42     
43     public Person(int age, double height) {
44         this.age = age;
45         this.height = height;
46     }
47 
48     @Override
49     public String toString() {
50         return "Person{" +
51                 "age=" + age +
52                 ", height=" + height +
53                 '}';
54     }
55 }
56 
57 class Student extends Person implements Serializable{
58     private String id;
59 
60     public Student(int age, double height, String id) {
61         super(age, height);
62         this.id = id;
63     }
64 
65     @Override
66     public String toString() {
67         return "Student{" +
68                 "age=" + age +
69                 ", height=" + height +
70                 ", id='" + id + '\'' +
71                 '}';
72     }
73 }

 

程序輸出產生異常:

java.io.InvalidClassException: Student; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at SerializableTest.testDeserialize(SerializableTest.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    ...

Process finished with exit code 0

 

當咱們爲Person類添加默認構造函數時:

 1 class Person{
 2     protected int age;
 3     protected double height;
 4 
 5     public Person() {
 6     }
 7 
 8     public Person(int age, double height) {
 9         this.age = age;
10         this.height = height;
11     }
12 
13     @Override
14     public String toString() {
15         return "Person{" +
16                 "age=" + age +
17                 ", height=" + height +
18                 '}';
19     }
20 }

 

程序輸出以下,咱們可觀察到,父類中的字段都是默認值,只有子類中的字段獲得了正確的序列化。出現這種狀況的緣由是子類並無擔負起序列化父類中字段的責任。

Student{age=0, height=0.0, id='1234'}
Student{age=0, height=0.0, id='5678'}

Process finished with exit code 0

 

爲了解決上述問題,咱們須要藉助上一節中學到的知識,使用自定義的序列化方法writeObject和readObject來主動將父類中的字段進行序列化。

 1 import org.junit.Test;
 2 
 3 import java.io.*;
 4 
 5 public class SerializableTest {
 6 
 7     @Test
 8     public void testSerialize() {
 9         Student one = new Student(12, 156.6, "1234");
10         Student two = new Student(16, 177.7, "5678");
11 
12         try (ObjectOutputStream output =
13                      new ObjectOutputStream(new FileOutputStream("Studnet.txt"))) {
14             output.writeObject(one);
15             output.writeObject(two);
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19     }
20 
21     @Test
22     public void testDeserialize() {
23 
24         try (ObjectInputStream input =
25                      new ObjectInputStream(new FileInputStream("Studnet.txt"))) {
26             Student one = (Student) input.readObject();
27             Student two = (Student) input.readObject();
28 
29             System.out.println(one);
30             System.out.println(two);
31         } catch (IOException e) {
32             e.printStackTrace();
33         } catch (ClassNotFoundException e) {
34             e.printStackTrace();
35         }
36     }
37 }
38 
39 class Person{
40     protected int age;
41     protected double height;
42 
43     public Person() {
44     }
45 
46     public Person(int age, double height) {
47         this.age = age;
48         this.height = height;
49     }
50 
51     @Override
52     public String toString() {
53         return "Person{" +
54                 "age=" + age +
55                 ", height=" + height +
56                 '}';
57     }
58 }
59 
60 class Student extends Person implements Serializable{
61     private String id;
62 
63     public Student(int age, double height, String id) {
64         super(age, height);
65         this.id = id;
66     }
67 
68     private void writeObject(java.io.ObjectOutputStream out)
69             throws IOException {
70         out.defaultWriteObject();
71         out.writeInt(age);
72         out.writeDouble(height);
73     }
74     
75     private void readObject(java.io.ObjectInputStream in)
76             throws IOException, ClassNotFoundException {
77         in.defaultReadObject();
78         this.age = in.readInt();
79         this.height = in.readDouble();
80     }
81 
82     @Override
83     public String toString() {
84         return "Student{" +
85                 "age=" + age +
86                 ", height=" + height +
87                 ", id='" + id + '\'' +
88                 '}';
89     }
90 }

 

程序輸出以下,能夠看到徹底正確。

Student{age=12, height=156.6, id='1234'}
Student{age=16, height=177.7, id='5678'}

Process finished with exit code 0

 

5、serialVersionUID的做用及自動生成

 

咱們知道,反序列化必須擁有class文件,但隨着項目的升級,class文件也會升級,序列化怎麼保證升級先後的兼容性呢?

 

java序列化提供了一個private static final long serialVersionUID 的序列化版本號,只有版本號相同,即便更改了序列化屬性,對象也能夠正確被反序列化回來。若是反序列化使用的class的版本號與序列化時使用的不一致,反序列化會報InvalidClassException異常。下面是JDK 8中ArrayList的源碼中的serialVersionUID。

 

 1 public class ArrayList<E> extends AbstractList<E>
 2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 3 {
 4     private static final long serialVersionUID = 8683452581122892189L;
 5 
 6     /**
 7      * Default initial capacity.
 8      */
 9     private static final int DEFAULT_CAPACITY = 10;
10     ...  
11 }

 

序列化版本號可自由指定,若是不指定,JVM會根據類信息本身計算一個版本號,這樣隨着class的升級,就沒法正確反序列化;不指定版本號另外一個明顯隱患是,不利於jvm間的移植,可能class文件沒有更改,但不一樣jvm可能計算的規則不同,這樣也會致使沒法反序列化。

 

什麼狀況下須要修改serialVersionUID呢?分三種狀況。

  • 若是隻是修改了方法,反序列化不容影響,則無需修改版本號
  • 若是隻是修改了靜態Field或瞬態Field,則反序列化不受任何影響
  •  若是修改類時修改了非靜態Field、非瞬態Field,則可能致使序列化版本不兼容。若是對象流中的對象和新類中包含同名的Field,而Field類型不一樣,則反序列化失敗,類定義應該更新serialVersionUID Field值。若是隻是新增了實例變量,則反序列化回來新增的是默認值;若是減小了實例變量,反序列化時會忽略掉減小的實例變量。

 

咱們在平常編程實踐中,通常會選擇使用IDE來自動生成serialVersionUID,這樣能夠最大化地減小重複的可能性。對於IntelliJ IDEA,自動生成serialVersionUID有三步:

  • 修改IDEA配置:File->Setting->Editor->Inspections->Serialization issues->Serializable class without ’serialVersionUID’

  • 類實現Serializable接口
  • 在類名上執行Alt+Enter,而後選擇生成serialVersionUID便可

 

6、序列化的缺點

 

Java序列化存在四個致命缺點,致使其不適用於網絡傳輸:

  • 沒法跨語言:在網絡傳輸中,常常會有異構語言的進程的交互,但Java序列化技術是Java語言內部的私有協議,其餘語言沒法進行反序列化。目前全部流行的RPC框架都沒有使用Java序列化做爲編解碼框架。
  • 潛在風險高:不可信流的反序列化可能致使遠程代碼執行(RCE)、拒絕服務(DoS)和一系列其餘攻擊。
  • 序列化後的碼流太大
  • 序列化的性能較低

 

在真正的生產環境中,通常會選擇其它編解碼框架,領先的跨平臺結構化數據表示是 JSON 和 Protocol Buffers,也稱爲 protobuf。JSON 由 Douglas Crockford 設計用於瀏覽器與服務器通訊,Protocol Buffers 由谷歌設計用於在其服務器之間存儲和交換結構化數據。JSON 和 protobuf 之間最顯著的區別是 JSON 是基於文本的,而且是人類可讀的,而 protobuf 是二進制的,但效率更高。

 

 7、參考文獻

 

  1. 《瘋狂Java講義》第2版,李剛著,電子工業出版社
  2. 《Java核心技術》第10版,霍斯特曼等著,機械工業出版本
  3. 《Netty權威指南》第2版,李林鋒著,電子工業出版社
  4. 《Effective Java》第2版,Joshua Bloch著,機械工業出版社
相關文章
相關標籤/搜索