分享

《从零开始搭建游戏服务器》 序列化工具(最优版Protostuff)

 WindySky 2017-08-13

前言:

之前使用protobuf工具来解析表格数据和定制网络协议,但是为了网络安全和压缩数据大小,有时候需要对数据进行序列化,这就需要设计一个序列化工具类来完成序列化和反序列化的操作。

框架的对比:

Java中几个常用的序列化框架对比,包括:kryoHessianProtostuffProtostuff-Runtimejava.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 的使用案例。
在原生的 Protostuff 中通过反射实例化java类的时候,是通过使用 Class.newInstance() 方法来实现的,而前提就是这个类java类必须提供默认构造函数。
假如希望在java类不提供默认构造函数的时候也能实现反射实例化,可以选择使用 objenesis 来实例化java类,使用方式可以参考 objenesis官网

3.框架选择:

综合上述的分析,最终我还是选择了 Protostuff 框架来完成Protobuf数据的序列化,关于不支持无默认构造函数类序列化的缺陷接下来通过使用 objenesis 也会得到解决。

自定义序列化工具类

这里我们创建此工具类,取名为 SerializationUtil,使用Protostuff来序列化和反序列化Protobuf数据:

1.库引入:

首先要在pom.xml里添加com.dyuproject.protostuffobjenesis的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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

上面是引入 objenesis 之后的版本,优点就是支持没有提供默认构造函数的java类的实例化,可以看到这里通过 Class<T> cls = (Class<T>) obj.getClass(); 来实例化java类的。ConcurrentHashMap是适用于高并发的Map数据结构。

3.工具类调用:

随便定义一个Protobuf的协议类,假设为User,下面就是具体的序列化工具使用操作:

  • 序列化:

    byte[] data = SerializationUtil.serialize(user,User.class);  
    • 1
    • 1
  • 反序列化:

    User user2 = SerializationUtil.deserialize(data,User.class); 
    • 1
    • 1

参考资料:

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多