JDK序列化方案
示例代码如下:
package com.zhuke.serial.jdk;
import com.zhuke.serial.jdk.entity.Apple;
import org.apache.commons.codec.binary.Hex;
import java.io.*;
import java.util.Arrays;
/**
* Created by ZHUKE on 2017/1/20.
*/
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Apple apple = new Apple();
defaultWriteObject(apple);
Apple object = (Apple) defaultReadObject("obj.serial");
System.out.println(object);
System.out.println("serial info: " + Hex.encodeHex(readSerialInfo("obj.serial")));
}
private static void defaultWriteObject(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("obj.serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.flush();
oos.close();
}
private static Object defaultReadObject(String path) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
Object read = ois.readObject();
ois.close();
return read;
}
private static byte[] readSerialInfo(String path) throws IOException {
FileInputStream fileInputStream = new FileInputStream(path);
byte[] b = new byte[1024];
int r = fileInputStream.read();
int len = 0;
for (len = 0; r != -1; len++) {
b[len] = (byte) r;
r = fileInputStream.read();
}
return Arrays.copyOfRange(b, 0, len);
}
}
16进制表示序列化后的信息为:
aced000573720021636f6d2e7a68756b652e73657269616c2e6a646b2e656e746974792e4170706c655a97b46baef464d90200014c00046e616d657400124c6a6176612f6c616e672f537472696e673b78720021636f6d2e7a68756b652e73657269616c2e6a646b2e656e746974792e467275697410be85a9044b3d4e02000149000677656967687478700000000a74000d44656661756c74204170706c65
其中各部分内容表示如下:
- 0xac 0xed: STREAM_MAGIC流的幻数,用于标记序列化协议
- 0x00 0x05: STREAM_VERSION标记序列化协议的版本
以上两类信息在ObjectOutputStream类对象构造时就被写入到了序列化的缓冲区中
new ObjectOutputStream(obj)
writeHeader()
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
- 0x73: TC_OBJECT. 声明这是一个新的对象
- 0x72: TC_CLASSDESC. 声明这是一个新的类描述
- 0x00 0x21: 类名长度,换算成十进制为33
- 636f6d2e7a68756b652e73657269616c2e6a646b2e656e746974792e4170706c65 表示类名称:com.zhuke.serial.jdk.entity.Apple
- 5a97b46baef464d9: 序列化ID,long型,占8位
- 0x02: 标记号,该字节的8位代表不同含义:
SC_EXTERNALIZABLE 0x04 : 该类实现了java.io.Externalizable接口
SC_BLOCK_DATA 0x08 : Externalizable接口的writeExternal方法写入的数据
SC_SERIALIZABLE 0x02 : 该类实现了java.io.Serializable接口
SC_WRITE_METHOD 0x01 : 该序列化类实现了writeObject方法
SC_ENUM 0x10 : 该类是枚举(enum)类型
该标记号通过上述信息进行或运算(|)而获得
- 0x00 0x01: 代表类属性域的个数
- 0x4c: 域类型,0x4c代表L,即该域为java对象类型
- 0x00 0x04: 域名称长度
- 0x6e 0x61 0x6d 0x65: 域名称name
- 0x74: TC_STRING 一个新字符串
- 0x00 0x12: 域类型长度
- 4c6a6176612f6c616e672f537472696e673b :对象类型签名Ljava/lang/String;
- 0x78: TC_ENDBLOCKDATA 对象数据块结束标志
- 0x72: TC_CLASSDESC 声明这是一个新的类描述
- 0x00 0x21: 类名长度,换算成十进制为33
- 636f6d2e7a68756b652e73657269616c2e6a646b2e656e746974792e4672756974: 类名
- 0x02: 标记号
- 标记号
- 域个数
- 域类型
- 域名称长度
- 域名称weight
- 对象结束标志
- 0x70: TC_NULL再没有父类的标志
总结:
JDK序列化的基本步骤:
a) 输出序列化的头部信息,包括标识序列化协议的幻数以及协议的版本
b) 按照由子类到父类的顺序,递归的输出类的描述信息,直到不再有父类为止;类描述信息按照类元数据,类属性信息的顺序写入序列化流中
c) 按照由父类到子类的顺序,递归的输出对象域的实际数据值;而对象的属性信息是按照基本数据类型到java对象类型的顺序写入序列化流中;其中java对象类型的属性会从步骤a)重新开始递归的输出,直到不再存在java对象类型的属性。
但是这种序列化是不安全,因为序列化二进制格式完全编写在文档中或者在网络中传播,并且完全可逆。可以通过自定义序列化的方法,将序列化的值进行加密后,解决安全问题。