我用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协议缓冲区对象数据?
答案 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有两个小问题:
InputStream
在方法read
中引发异常。因此,为了解决这个问题,我在下面的代码中做了一些小的改进:
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 ...