有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何最好地指定与Netty一起使用的Protobuf(最好使用内置Protobuf支持)

我在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;
}

这是最优的吗? 我是否会更好地使用extends,或者使用enum的其他用法

甚至,我应该创建一个全新的OneToOneDecoder,它可以通过某种头来识别消息类型吗?我可以这么做,但我不想

谢谢


共 (3) 个答案

  1. # 1 楼答案

    看起来你们已经非常接近/已经在使用谷歌的protobufs技术之一,叫做Union Types

    要点是你有一个专用的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
    
    }
    
  2. # 2 楼答案

    另一种方法是使用protobuf支持的扩展机制。我在联合类型太大的情况下使用这种方法

  3. # 3 楼答案

    我最初使用扩展机制解决了同样的问题,我记录了here

    但我发现Java中处理扩展所需的代码非常难看和冗长,所以我切换到前面描述的Union方法。由于生成的Java代码提供了一种一次性获取和构建每条消息的方法,因此代码更加简洁

    我使用两种机制来决定提取哪个可选消息。当需要性能时,我使用另一个答案中描述的switch方法,当性能不是问题并且我不想维护switch语句时,我使用反射方法,我只为每条消息创建一个句柄(消息)。下面给出了反射方法的一个示例,在我的例子中,java包装器是一个名为Commands的类,由Netty为我解码。它首先尝试找到一个将特定消息作为参数的处理程序,如果失败,它将使用camel case名称调用一个方法。为此,枚举必须是camel case消息的下划线名称

    // 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());
    }