之前使用protobuf工具來解析表格數(shù)據(jù)和定制網(wǎng)絡(luò)協(xié)議,但是為了網(wǎng)絡(luò)安全和壓縮數(shù)據(jù)大小,有時候需要對數(shù)據(jù)進行序列化,這就需要設(shè)計一個序列化工具類來完成序列化和反序列化的操作。
Java中幾個常用的序列化框架對比,包括:kryo
、Hessian
、Protostuff
、Protostuff-Runtime
和java.io
:
框架 | 優(yōu)點 | 缺點 |
---|---|---|
kryo | 速度快,序列化后體積小 | 跨語言支持比較復(fù)雜 |
Hessian | 默認支持跨語言 | 速度比較慢 |
java.io | JDK自帶功能,使用方便,可序列化所有類 | 速度慢,占空間大 |
Protostuff | 速度快,基于protobuf | 需要靜態(tài)編譯 |
Protostuff-Runtime | 無需靜態(tài)編譯,但序列化之前需要預(yù)先傳入Schema | 不支持無默認構(gòu)造函數(shù)的類,反序列化時需要用戶自己初始化序列化后的對象,而此工具只負責(zé)對初始化后的對象進行賦值 |
protobuf
的一個缺點是需要數(shù)據(jù)結(jié)構(gòu)的預(yù)編譯過程,首先要編寫 .proto
格式的配置文件,再通過 protobuf
提供的工具生成各種語言響應(yīng)的代碼。由于java具有反射和動態(tài)代碼生成的能力,這個預(yù)編譯過程不是必須的,可以在代碼執(zhí)行時來實現(xiàn)。有個 protostuff插件 已經(jīng)實現(xiàn)了這個功能。
protostuff
基于Google protobuf,但是提供了更多的功能和更簡易的用法。其中,protostuff-runtime
實現(xiàn)了無需預(yù)編譯對Java bean進行protobuf序列化/反序列化的能力。protostuff-runtime
的局限是序列化前需預(yù)先傳入schema
,反序列化不負責(zé)對象的創(chuàng)建只負責(zé)復(fù)制,因而必須提供默認構(gòu)造函數(shù)。此外,protostuff
還可以按照protobuf的配置序列化成json/yaml/xml等格式。
沒經(jīng)過修改過的 protostuff
,性能方法還是有些缺陷,這里我在一篇關(guān)于 輕量級分布式 RPC 框架 博客中找到了據(jù)說是當(dāng)前性能最優(yōu)的 優(yōu)化版Protostuff 的使用案例。
在原生的 Protostuff
中通過反射實例化java類的時候,是通過使用 Class.newInstance()
方法來實現(xiàn)的,而前提就是這個類java類必須提供默認構(gòu)造函數(shù)。
假如希望在java類不提供默認構(gòu)造函數(shù)的時候也能實現(xiàn)反射實例化,可以選擇使用 objenesis
來實例化java類,使用方式可以參考 objenesis官網(wǎng)。
綜合上述的分析,最終我還是選擇了 Protostuff
框架來完成Protobuf數(shù)據(jù)的序列化,關(guān)于不支持無默認構(gòu)造函數(shù)類序列化的缺陷接下來通過使用 objenesis
也會得到解決。
這里我們創(chuàng)建此工具類,取名為 SerializationUtil
,使用Protostuff
來序列化和反序列化Protobuf數(shù)據(jù):
首先要在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>
主要包含兩個核心的函數(shù):序列化函數(shù) Serializer
和反序列化函數(shù) 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
之后的版本,優(yōu)點就是支持沒有提供默認構(gòu)造函數(shù)的java類的實例化,可以看到這里通過 Class<T> cls = (Class<T>) obj.getClass();
來實例化java類的。ConcurrentHashMap
是適用于高并發(fā)的Map數(shù)據(jù)結(jié)構(gòu)。
隨便定義一個Protobuf的協(xié)議類,假設(shè)為User
,下面就是具體的序列化工具使用操作:
序列化:
byte[] data = SerializationUtil.serialize(user,User.class);
反序列化:
User user2 = SerializationUtil.deserialize(data,User.class);