我在protocol buffers中指定了一个协议。传输层正在利用Netty的协议缓冲支持 - 重要的是,Netty的ProtobufDecoder只接受一种MessageLite类型。
现在,我想在此频道下发送各种不同的消息类型,每个子类型都有与之关联的结构化信息。协议缓冲区没有继承机制,所以我使用的是一种组合。我不确定我是否以正确的方式去做。
我的方法是使用枚举对我的不同事件进行分类,并使用可选成员封装它们的差异。请参阅下面的.proto
,为了清楚起见,我对其进行了简化。
我的问题是接收代码需要在EventType.ERROR和ErrorEventDetail之间建立关联。这只是感觉有点笨拙。
简化Events.proto
:
package events;
option java_package = "com.example";
option java_outer_classname = "EventProtocol";
message Event {
enum EventType {
START = 0;
DELEGATE = 1;
ERROR = 2;
STOP = 3;
}
required events.Event.EventType event_type = 1 [default = START];
required int32 id = 2;
required int64 when = 3;
optional StartEventDetail start_event_detail = 4;
optional DelegateEventDetail delegate_event_detail = 5;
optional ErrorEventDetail error_event_detail = 6;
optional StopEventDetail stop_event_detail = 7;
}
message StartEventDetail {
required string object_name = 1;
}
message DelegateEventDetail {
required int32 object_id = 2;
required string task = 3;
}
message ErrorEventDetail {
required string text = 1;
required int32 error_code = 2;
optional Event cause = 3;
}
message StopEventDetail {
required int32 object_id = 2;
}
这是最佳的吗?
我会以某种方式更好地使用扩展,或者使用enum
吗?
甚至,我是否应该创建一个全新的OneToOneDecoder,它可以通过某种标头识别消息类型?我能做到这一点,但我宁愿不......
由于
答案 0 :(得分:6)
好像你已经非常接近/已经使用了一种名为Union Types
的Google的protobufs技术要点是,您有一个专用的type
字段,您可以“切换”以了解要获取的消息:
message OneMessage {
enum Type { FOO = 1; BAR = 2; BAZ = 3; }
// Identifies which field is filled in.
required Type type = 1;
// One of the following will be filled in.
optional Foo foo = 2;
optional Bar bar = 3;
optional Baz baz = 4;
}
其中Foo,Bar和Baz可以在其他文件中定义为单独的消息。你可以打开类型来获取实际的有效负载(它是Scala,但你可以用Java的switch
做同样的事情):
OneMessage.getType match {
case OneMessage.Type.FOO =>
val foo = OneMessage.getFoo
// do the processing
true
case OneMessage.Type.BAR =>
val bar = OneMessage.getBar
// do the processing
true
case OneMessage.Type.BAZ =>
val baz = OneMessage.getBaz
// do the processing
true
}
答案 1 :(得分:3)
我最初使用扩展机制解决了同样的问题,我记录了here
但我发现处理扩展所需的Java代码非常丑陋且冗长,所以我按照描述改用了Union方法。代码更加清晰,因为生成的Java代码提供了一次获取和构建每条消息的方法。
我使用两种机制来决定要提取哪个可选消息。我使用另一个答案中描述的switch方法,当需要性能时,我使用反射方法,当性能不是问题而我不想维护一个switch语句,我只是为每个创建一个句柄(Message)信息。下面给出了一个反射方法的例子,在我的例子中,java包装器是一个名为Commands的类,由Netty为我解码。它首先尝试查找具有特定消息作为参数的处理程序,然后如果失败则使用驼峰案例名称调用方法。为此,Enum必须是驼峰案例消息的下划线名称。
// Helper that stops me having to create a switch statement for every command
// Relies on the Cmd enum naming being uppercase version of the sub message field names
// Will call the appropriate handle(Message) method by reflection
// If it is a command with no arguments, therefore no sub message it
// constructs the method name from the camelcase of the command enum
private MessageLite invokeHandler(Commands.Command cmd) throws Exception {
Commands.Command.Cmd com= cmd.getCmd();
//String name= CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_UNDERSCORE, com.name());
String name= com.name().toLowerCase();
jlog.debug("invokeHandler() - Looking up {} from {}", name, com.name());
FieldDescriptor field= Commands.Command.getDescriptor().findFieldByName(name);
if(field != null) {
// if we have a matching field then extract it and call the handle method with that as a parameter
Object c = cmd.getField(field);
jlog.debug("invokeHandler() - {}\n{}", c.getClass().getCanonicalName(), c);
Method m = getClass().getDeclaredMethod("handle", String.class, c.getClass());
return (MessageLite) m.invoke(this, cmd.getUser(), c);
}
// else we call a method with the camelcase name of the Cmd, this is for commands that take no arguments other than the user
String methodName= "handle"+CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, com.name());
jlog.debug("invokeHandler() - using method: {}", methodName);
Method m = getClass().getDeclaredMethod(methodName, String.class);
return (MessageLite) m.invoke(this, cmd.getUser());
}
答案 2 :(得分:0)
另一种方法是使用protobuf支持的扩展机制。我在union类型太大的情况下使用这种方法。