《從零開始搭建遊戲伺服器》 序列化工具(最優版Protostuff)
前言:
之前使用protobuf工具來解析表格資料和定製網路協議,但是為了網路安全和壓縮資料大小,有時候需要對資料進行序列化,這就需要設計一個序列化工具類來完成序列化和反序列化的操作。
框架的對比:
Java中幾個常用的序列化框架對比,包括:kryo
、Hessian
、Protostuff
、Protostuff-Runtime
和java.io
:
框架 | 優點 | 缺點 |
---|---|---|
kryo | 速度快,序列化後體積小 | 跨語言支援比較複雜 |
Hessian | 預設支援跨語言 | 速度比較慢 |
java.io | JDK自帶功能,使用方便,可序列化所有類 | 速度慢,佔空間大 |
Protostuff | 速度快,基於protobuf | 需要靜態編譯 |
Protostuff-Runtime | 無需靜態編譯,但序列化之前需要預先傳入Schema |
不支援無預設建構函式的類,反序列化時需要使用者自己初始化序列化後的物件,而此工具只負責對初始化後的物件進行賦值 |
1.詳細分析:
protobuf
的一個缺點是需要資料結構的預編譯過程,首先要編寫.proto
格式的配置檔案,再通過protobuf
提供的工具生成各種語言響應的程式碼。由於java具有反射和動態程式碼生成的能力,這個預編譯過程不是必須的,可以在程式碼執行時來實現。有個 protostuff外掛protostuff
基於Google protobuf,但是提供了更多的功能和更簡易的用法。其中,protostuff-runtime
實現了無需預編譯對java bean進行protobuf序列化/反序列化的能力。protostuff-runtime
的侷限是序列化前需預先傳入schema
,反序列化不負責物件的建立只負責複製,因而必須提供預設建構函式。此外,protostuff
還可以按照protobuf的配置序列化成json/yaml/xml等格式。
2.坑點解決:
沒經過修改過的 protostuff
,效能方法還是有些缺陷,這裡我在一篇關於 輕量級分散式 RPC 框架
在原生的
Protostuff
中通過反射例項化java類的時候,是通過使用 Class.newInstance()
方法來實現的,而前提就是這個類java類必須提供預設建構函式。 假如希望在java類不提供預設建構函式的時候也能實現反射例項化,可以選擇使用
objenesis
來例項化java類,使用方式可以參考 objenesis官網。
3.框架選擇:
綜合上述的分析,最終我還是選擇了 Protostuff
框架來完成Protobuf資料的序列化,關於不支援無預設建構函式類序列化的缺陷接下來通過使用 objenesis
也會得到解決。
自定義序列化工具類
這裡我們建立此工具類,取名為 SerializationUtil
,使用Protostuff
來序列化和反序列化Protobuf資料:
1.庫引入:
首先要在pom.xml裡新增com.dyuproject.protostuff
和objenesis
的jar包:
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.0.8</version>
</dependency>
<!-- Objenesis -->
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.1</version>
</dependency>
2.工具類編寫:
主要包含兩個核心的函式:序列化函式 Serializer
和反序列化函式 Deserializer
:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
public class SerializationUtil {
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
private static Objenesis objenesis = new ObjenesisStd(true);
private SerializationUtil() {
}
@SuppressWarnings("unchecked")
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
if (schema != null) {
cachedSchema.put(cls, schema);
}
}
return schema;
}
@SuppressWarnings("unchecked")
public static <T> byte[] serialize(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
public static <T> T deserialize(byte[] data, Class<T> cls) {
try {
T message = (T) objenesis.newInstance(cls);
Schema<T> schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
上面是引入 objenesis
之後的版本,優點就是支援沒有提供預設建構函式的java類的例項化,可以看到這裡通過 Class<T> cls = (Class<T>) obj.getClass();
來例項化java類的。ConcurrentHashMap
是適用於高併發的Map資料結構。
3.工具類呼叫:
隨便定義一個Protobuf的協議類,假設為User
,下面就是具體的序列化工具使用操作:
序列化:
byte[] data = SerializationUtil.serialize(user,User.class);
反序列化:
User user2 = SerializationUtil.deserialize(data,User.class);