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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://linsh-tech.blog.csdn.net/article/details/72983545

前言:

之前使用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>

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); 

参考资料:

展开阅读全文

没有更多推荐了,返回首页