Java 序列化/反序列化
在Java中實現對象反序列化非常簡單,實現java.io.Serializable(內部序列化)或java.io.Externalizable(外部序列化)接口即可被序列化(Externalizable接口只是實現了java.io.Serializable接口)。
反序列化類對象時有如下限制:
- 被反序列化的類必須存在。
serialVersionUID值必須一致。
除此之外,反序列化類對象是不會調用該類構造方法的,因為在反序列化創建類實例時使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創建了一個反序列化專用的Constructor(反射構造方法對象),使用這個特殊的Constructor可以繞過構造方法創建類實例(前面章節講sun.misc.Unsafe 的時候我們提到了使用allocateInstance方法也可以實現繞過構造方法創建類實例)。
使用反序列化方式創建類實例代碼片段:
package com.anbai.sec.serializes;
import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
/**
* 使用反序列化方式在不調用類構造方法的情況下創建類實例
* Creator: yz
* Date: 2019/12/20
*/
public class ReflectionFactoryTest {
public static void main(String[] args) {
try {
// 獲取sun.reflect.ReflectionFactory對象
ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
// 使用反序列化方式獲取DeserializationTest類的構造方法
Constructor constructor = factory.newConstructorForSerialization(
DeserializationTest.class, Object.class.getConstructor()
);
// 實例化DeserializationTest對象
System.out.println(constructor.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序運行結果:
com.anbai.sec.serializes.DeserializationTest@2b650cea
具體細節可參考 不用構造方法也能創建對象。
ObjectInputStream、ObjectOutputStream
java.io.ObjectOutputStream類最核心的方法是writeObject方法,即序列化類對象。
java.io.ObjectInputStream類最核心的功能是readObject方法,即反序列化類對象。
所以,只需借助ObjectInputStream和ObjectOutputStream類我們就可以實現類的序列化和反序列化功能了。
java.io.Serializable
java.io.Serializable是一個空的接口,我們不需要實現java.io.Serializable的任何方法,代碼如下:
public interface Serializable {
}
您可能會好奇我們實現一個空接口有什么意義?其實實現java.io.Serializable接口僅僅只用于標識這個類可序列化。實現了java.io.Serializable接口的類原則上都需要生產一個serialVersionUID常量,反序列化時如果雙方的serialVersionUID不一致會導致InvalidClassException 異常。如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認 serialVersionUID值。
DeserializationTest.java測試代碼如下:
package com.anbai.sec.serializes;
import java.io.*;
import java.util.Arrays;
/**
* Creator: yz
* Date: 2019/12/15
*/
public class DeserializationTest implements Serializable {
private String username;
private String email;
// 省去get/set方法....
public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// 創建DeserializationTest類,并類設置屬性值
DeserializationTest t = new DeserializationTest();
t.setUsername("yz");
t.setEmail("admin@javaweb.org");
// 創建Java對象序列化輸出流對象
ObjectOutputStream out = new ObjectOutputStream(baos);
// 序列化DeserializationTest類
out.writeObject(t);
out.flush();
out.close();
// 打印DeserializationTest類序列化以后的字節數組,我們可以將其存儲到文件中或者通過Socket發送到遠程服務地址
System.out.println("DeserializationTest類序列化后的字節數組:" + Arrays.toString(baos.toByteArray()));
// 利用DeserializationTest類生成的二進制數組創建二進制輸入流對象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// 通過反序列化輸入流(bais),創建Java對象輸入流(ObjectInputStream)對象
ObjectInputStream in = new ObjectInputStream(bais);
// 反序列化輸入流數據為DeserializationTest對象
DeserializationTest test = (DeserializationTest) in.readObject();
System.out.println("用戶名:" + test.getUsername() + ",郵箱:" + test.getEmail());
// 關閉ObjectInputStream輸入流
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
程序執行結果如下:
DeserializationTest類序列化后的字節數組:[-84, -19, 0, 5, 115, 114, 0, 44, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 68, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 84, 101, 115, 116, 74, 36, 49, 16, -110, 39, 13, 76, 2, 0, 2, 76, 0, 5, 101, 109, 97, 105, 108, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 117, 115, 101, 114, 110, 97, 109, 101, 113, 0, 126, 0, 1, 120, 112, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 116, 0, 2, 121, 122]
用戶名:yz,郵箱:admin@javaweb.org
核心邏輯其實就是使用ObjectOutputStream類的writeObject方法序列化DeserializationTest類,使用ObjectInputStream類的readObject方法反序列化DeserializationTest類而已。
簡化后的代碼片段如下:
// 序列化DeserializationTest類
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(t);
// 反序列化輸入流數據為DeserializationTest對象
ObjectInputStream in = new ObjectInputStream(bais);
DeserializationTest test = (DeserializationTest) in.readObject();
ObjectOutputStream序列化類對象的主要流程是首先判斷序列化的類是否重寫了writeObject方法,如果重寫了就調用序列化對象自身的writeObject方法序列化,序列化時會先寫入類名信息,其次是寫入成員變量信息(通過反射獲取所有不包含被transient修飾的變量和值)。
java.io.Externalizable
java.io.Externalizable和java.io.Serializable幾乎一樣,只是java.io.Externalizable接口定義了writeExternal和readExternal方法需要序列化和反序列化的類實現,其余的和java.io.Serializable并無差別。
java.io.Externalizable.java:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
ExternalizableTest.java測試代碼如下:
package com.anbai.sec.serializes;
import java.io.*;
import java.util.Arrays;
/**
* Creator: yz
* Date: 2019/12/15
*/
public class ExternalizableTest implements java.io.Externalizable {
private String username;
private String email;
// 省去get/set方法....
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(email);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.username = (String) in.readObject();
this.email = (String) in.readObject();
}
public static void main(String[] args) {
// 省去測試代碼,因為和DeserializationTest一樣...
}
}
程序執行結果如下:
ExternalizableTest類序列化后的字節數組:[-84, -19, 0, 5, 115, 114, 0, 43, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 69, 120, 116, 101, 114, 110, 97, 108, 105, 122, 97, 98, 108, 101, 84, 101, 115, 116, -122, 124, 92, -120, -52, 73, -100, 6, 12, 0, 0, 120, 112, 116, 0, 2, 121, 122, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 120]
ExternalizableTest類反序列化后的字符串:??sr+com.anbai.sec.serializes.ExternalizableTest?|\??I?xptyztadmin@javaweb.orgx
用戶名:yz,郵箱:admin@javaweb.org
鑒于兩者之間沒有多大差別,這里就不再贅述。
自定義序列化(writeObject)和反序列化(readObject)
實現了java.io.Serializable接口的類還可以定義如下方法(反序列化魔術方法)將會在類序列化和反序列化過程中調用:
private void writeObject(ObjectOutputStream oos),自定義序列化。private void readObject(ObjectInputStream ois),自定義反序列化。private void readObjectNoData()。protected Object writeReplace(),寫入時替換對象。protected Object readResolve()。
具體的方法名定義在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)方法有詳細的聲明。
序列化時可自定義的方法示例代碼:
public class DeserializationTest implements Serializable {
/**
* 自定義反序列化類對象
*
* @param ois 反序列化輸入流對象
* @throws IOException IO異常
* @throws ClassNotFoundException 類未找到異常
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("readObject...");
// 調用ObjectInputStream默認反序列化方法
ois.defaultReadObject();
// 省去調用自定義反序列化邏輯...
}
/**
* 自定義序列化類對象
*
* @param oos 序列化輸出流對象
* @throws IOException IO異常
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
System.out.println("writeObject...");
// 省去調用自定義序列化邏輯...
}
private void readObjectNoData() {
System.out.println("readObjectNoData...");
}
/**
* 寫入時替換對象
*
* @return 替換后的對象
*/
protected Object writeReplace() {
System.out.println("writeReplace....");
return null;
}
protected Object readResolve() {
System.out.println("readResolve....");
return null;
}
}
當我們序列化DeserializationTest類時,會自動調用(反射)該類的writeObject(ObjectOutputStream oos)方法,反序列化時候也會自動調用readObject(ObjectInputStream)方法,也就是說我們可以通過在需要序列化/反序列化的類中定義readObject和writeObject方法從而實現自定義的序列化和反序列化操作,和當然前提是被序列化的類必須有此方法且方法的修飾符必須是private。
Java Web安全
推薦文章: