使用kryo序列化protobuf的问题

时间:2016-10-26 03:54:18

标签: serialization protocol-buffers kryo

我用Java实现了自己的序列化程序。让我们称之为abcSerializer。我尝试序列化的对象是abc,它是一个Google Protocol Buffer类。

我正在尝试使用kryo框架来序列化这个对象。经过一些研究和阅读谷歌,我决定继续使用kryo序列化器。我没有指定任何序列化器本身,所以我假设kryo选择一个默认的序列化器。

public class abcSerializer implements AttributeSerializer <abc> {

  public abcSerializer() {
      kryo = new Kryo();
  }

  public static Kryo getKryo() {
      return kryo;
  }

  @Override
  public abc read(byte[] buffer) {

    abc xyz = null;

    ByteArrayInputStream abcsStream = new ByteArrayInputStream(buffer);
    Input abcsStreamInput = new Input(abcsStream);
    xyz = getKryo().readObject(abcsStreamInput, abc.class);
    return xyz;
}

@Override
public void write(byte[] buffer, abc abc) {

   ByteArrayOutputStream abcStream = new ByteArrayOutputStream();
   Output abcOutput = new Output(abcStream);

   getKryo().writeObject(abcOutput, abc);

   abcOutput.toBytes()        
}

}

当我写writeObject时,一切都很好。但是,当我执行readObject时会出现问题。 Kyro引发了以下异常。

com.esotericsoftware.kryo.KryoException: Class cannot be created (missing no-arg constructor): java.util.Collections$Unmodifiabljava.lang.IllegalStateException: Encountered error in deserializer [null value returned]. Check serializer compatibility.eRandomAccessList

以上异常非常自我解释。

kryo文档如下。 “”特定类型的序列化程序使用Java代码创建该类型的新实例。诸如FieldSerializer之类的序列化程序是通用的,必须处理创建任何类的新实例。默认情况下,如果一个类具有零参数构造函数,则通过ReflectASM或反射调用它,否则抛出异常。如果零参数构造函数是私有的,则尝试使用setAccessible通过反射访问它。如果这是可以接受的,私有零​​参数构造函数是允许Kryo创建类实例而不影响公共API的好方法。“”

现在,我有两个问题。

1)google协议缓冲区生成的类确实没有arg构造函数。然而,这似乎是私人的。这是一个问题,也是上述kryo异常的根本原因?

2)如果是这样,如何推进上述问题?我的意思是,我如何编写自己的序列化程序并仍然用于序列化google协议缓冲区对象数据?

2 个答案:

答案 0 :(得分:0)

这对你有用吗?

/**
 * This lets Kryo serialize protobufs more efficiently.
 */
public class ProtobufKryo<P extends GeneratedMessage> extends Serializer<P> {
    protected final Method parser;

    public ProtobufKryo(Class<P> theClass) {
        try {
            parser = theClass.getDeclaredMethod("parseFrom", InputStream.class);
            parser.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(theClass.toString() + " doesn't have parser");
        }
    }

    @Override
    public void write(Kryo kryo, Output output, P generatedMessage) {
        try {
            generatedMessage.writeTo(output);
        } catch (IOException e) {
            // This isn't supposed to happen with a Kryo output.
            throw new RuntimeException(e);
        }
    }

    @Override
    public P read(Kryo kryo, Input input, Class<P> gmClass) {
        try {
            return (P)parser.invoke(null, input);
        } catch (InvocationTargetException | IllegalAccessException e) {
            // These really shouldn't happen
            throw new IllegalArgumentException(e);
        }
    }
}

好的,一些解释......

当Kryo遇到它无法识别的类的对象时,它会回退到Java序列化。并不是每一项都有效,有时也行不通。

(好吧,我承认上述情况可能并非总是如此。它可能是Kryo配置的一部分。在我工作的环境中 是真的。)

您可以告诉它对特定类使用自己的序列化,但有时您可以通过为特定类创建自定义序列化程序来做得更好。

上述内容利用了protobufs在Kryo中的现有序列化。基本上,它使用现有的protobuf writeTo()parseFrom()来处理Kryo中的序列化。您将注册上面的类来序列化每个protobuf类。 (Protobuf类扩展GeneratedMessage。)

写出对象只是使用普通的protobuf writeTo()方法。回读protobuf使用类parseFrom()方法,它通过构造函数中的反射找到。

因此,您可以使用以下内容配置序列化程序:

  Kryo k = new Kryo();
  k.addDefaultSerializer(MyProtobuf.class, ProtobufKryo.class);
  k.addDefaultSerializer(MyOtherProtobuf.class, ProtobufKryo.class);

答案 1 :(得分:0)

我发现DaveWill's的上一个answer有两个小问题:

  1. 使用InputStream在方法read中引发异常。
  2. 使用Protobuf生成的具有许多嵌套类/消息的类时,有时写类名很麻烦。

因此,为了解决这个问题,我在下面的代码中做了一些小的改进:

  • InputStream数组代替byte[]
  • 添加了一个辅助程序registerMessagesFrom,用于查找Protobuf生成的顶级类中的所有嵌套类/消息。
package com.juarezr.serialization;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.google.protobuf.AbstractMessage;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ProtobufSerializer<T extends AbstractMessage> extends Serializer<T> implements Serializable {
    
    static final long serialVersionUID = 1667386898559074449L;
    protected final Method parser;

    public ProtobufSerializer(final Class<T> protoMessageClass) {
        try {
            this.parser = protoMessageClass.getDeclaredMethod("parseFrom", byte[].class);
            this.parser.setAccessible(true);
        } catch (SecurityException | NoSuchMethodException ex) {
            throw new IllegalArgumentException(protoMessageClass.toString() + " doesn't have a protobuf parser", ex);
        }
    }

    @Override
    public void write(final Kryo kryo, final Output output, final T protobufMessage) {
        if (protobufMessage == null) {
            output.writeByte(Kryo.NULL);
            output.flush();
            return;
        }
        final byte[] bytes = protobufMessage.toByteArray();
        output.writeInt(bytes.length + 1, true);
        output.writeBytes(bytes);
        output.flush();
    }

    @SuppressWarnings({"unchecked", "JavaReflectionInvocation"})
    @Override
    public T read(final Kryo kryo, final Input input, final Class<T> protoMessageClass) {
        final int length = input.readInt(true);
        if (length == Kryo.NULL) {
            return null;
        }
        final Object bytesRead = input.readBytes(length - 1);
        try {
            final Object parsed = this.parser.invoke(protoMessageClass, bytesRead);
            return (T) parsed;
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("Unable to deserialize protobuf for class: " + protoMessageClass.getName(), e);
        }
    }

    @Override
    public boolean getAcceptsNull() {
        return true;
    }

    @SuppressWarnings("unchecked")
    public static <M extends AbstractMessage> void registerMessagesFrom(final M rootMessage, final Kryo kryo) {

        final Class<M> messageClass = (Class<M>) rootMessage.getClass();
        final ProtobufSerializer<M> serializer = new ProtobufSerializer<>(messageClass);
        kryo.register(messageClass, serializer);

        final Class<?>[] nestedClasses = messageClass.getDeclaredClasses();
        for (final Class<?> innerClass : nestedClasses) {
            if ((AbstractMessage.class).isAssignableFrom(innerClass)) {
                final Class<M> typedClass = (Class<M>) innerClass;
                final ProtobufSerializer<M> serializer2 = new ProtobufSerializer<>(typedClass);
                kryo.register(typedClass, serializer2);
            }
        }
    }
}

您可以使用以下方式配置序列化:

// ...
final Kryo kryo = new Kryo();

ProtobufSerializer.registerMessagesFrom(MyProtoEnclosingClass.MyProtoTopLevelClass.getDefaultInstance(), kryo);

// Add a registration for each generated file and top level class ...