如何直接从protobuf创建GRPC客户端而不将其编译成java代码

mtS*_*Chi 9 java protocol-buffers grpc grpc-java

使用 gRPC 时,我们需要通过协议缓冲区编译器 (protoc) 或使用 Gradle 或 Maven protoc 构建插件从 .proto 服务定义生成 gRPC 客户端和服务器接口。

Flow now: protobuf file -> java code -> gRPC client.
Run Code Online (Sandbox Code Playgroud)

那么,有没有办法可以跳过这一步呢?

如何创建一个通用的 gRPC 客户端,可以直接从 protobuf 文件调用服务器,而无需编译成 java 代码?或者,有没有办法在运行时生成代码?

Flow expect: protobuf file -> gRPC client.
Run Code Online (Sandbox Code Playgroud)

我想构建一个通用的 gRPC 客户端系统,输入是 protobuf 文件以及方法、包、消息请求的描述......而不必为每个 protobuf 再次编译。

非常感谢。

Eri*_*son 9

Protobuf 系统确实需要 protoc 才能运行。但是,可以跳过生成的代码。您可以传递将文件解析为描述文件的内容,而不是将类似 和 的--java_out内容传递给协议。描述符文件是原始编码的. 这与反射服务使用的基本格式相同。--grpc_java_out--descriptor_set_out=FILE.protoFileDescriptorSet

一旦有了描述符,您就可以一次加载一个 FileDescriptor创建一个 DynamicMessage

然后对于 gRPC 部分,您需要创建一个 gRPC MethodDescriptor。

static MethodDescriptor from(
  Descriptors.MethodDescriptor methodDesc
) {
  return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
    // UNKNOWN is fine, but the "correct" value can be computed from
    // methodDesc.toProto().getClientStreaming()/getServerStreaming()
    .setType(getMethodTypeFromDesc(methodDesc))
    .setFullMethodName(MethodDescriptor.generateFullMethodName(
        serviceDesc.getFullName(), methodDesc.getName()))
    .setRequestMarshaller(ProtoUtils.marshaller(
        DynamicMessage.getDefaultInstance(methodDesc.getInputType())))
    .setResponseMarshaller(ProtoUtils.marshaller(
        DynamicMessage.getDefaultInstance(methodDesc.getOutputType())))
    .build();

static MethodDescriptor.MethodType getMethodTypeFromDesc(
  Descriptors.MethodDescriptor methodDesc
) {
  if (!methodDesc.isServerStreaming()
    && !methodDesc.isClientStreaming()) {
    return MethodDescriptor.MethodType.UNARY;
  } else if (methodDesc.isServerStreaming()
        && !methodDesc.isClientStreaming()) {
    return MethodDescriptor.MethodType.SERVER_STREAMING;
  } else if (!methodDesc.isServerStreaming()) {
    return MethodDescriptor.MethodType.CLIENT_STREAMING);
  } else {
    return MethodDescriptor.MethodType.BIDI_STREAMING);
  }
}
Run Code Online (Sandbox Code Playgroud)

到那时,您就拥有了所需的一切,并且可以Channel.newCall(method, CallOptions.DEFAULT)在 gRPC 中调用。您还可以自由地使用ClientCalls与存根 API 更相似的东西。

因此动态调用绝对是可能的,并且用于grpcurl之类的事情。但这也并不容易,因此通常只在必要时才进行。


cre*_*oup 0

从技术上讲,两者都是可能的。

代码生成器只是生成一些类;主要是protobuf消息、grpc方法描述符和存根。您可以实现它或签入生成的代码以绕过代码生成。我不确定这样做有什么好处。另外,如果原型改变了,也会很烦人。

只要您签入一些接口/抽象类来表示那些生成的存根/方法描述符和 protobuf 消息,也可以使用字节代码生成器动态地执行此操作。您必须确保这些非动态代码与原型定义同步(最有可能的是运行时检查/异常)。