1. 程式人生 > >gRPC Java程式碼生成

gRPC Java程式碼生成

本文講述protocol buffer編譯器會由協議定義檔案生成什麼樣的程式碼。proto2和proto3的區別將被高亮——注意,本文說的是存在於生成程式碼中的區別,不是基本的訊息類/介面——它們在這兩個版本中是一樣的。在開始本文之前,你應該先看一下proto2的語言指南和proto3語言指南。

編譯器呼叫

Protocol buffer 編譯器在遇到 –java_out= 命令列標識時會產生Java輸出。
–java_out=選項引數指定編譯器的輸出目錄。編譯器為每一個.proto檔案建立一個.java檔案。這個檔案包含一個java外部類,其中定義了一些基於.proto檔案中宣告的內部類和一些靜態欄位。

外部類的名產生規則:如果.proto檔案包含了如下的一行:

option java_outer_classname = "Foo";

外部類的名字將會是 Foo。否則,外部類的名字會變成由.proto檔名轉換而成的駝峰形式。例如,foo_bar.proto將會變成FooBar。如果該檔案中已經有一個訊息有同樣的名字,“OuterClass”將會被追加到外部類的名字後邊。例如,如果foo_bar.proto包含一個叫FooBar的訊息,外部類會變成FooBarOuterClass。

Java的包名會由下邊的Package來決定。

輸出什麼樣的檔案完全由–java_out選項,包名 ( .s 被替換成 /s),和.java檔名來決定。

舉個例子,你可以像下邊這樣呼叫編譯器:

protoc --proto_path=src --java_out=build/gen src/foo.proto

如果foo.proto的java包是com.example,它的外部類名是FooProtos,那麼protocol buffer編譯器會生成檔案:build/gen/com/example/FooProtos.java。Protocol buffer編譯器將會自動建立build/gen/com/example目錄,如果需要的話。但是,它不會建立build/gen或者build目錄;它們必須已經存在。你可以在單個命令呼叫中指定多個.proto檔案;所有輸出檔案會一次生成。

protoc的使用:

$ protoc.exe -h
Usage: D:\DEV\grpc\protoc-3.0.0-win32\bin\protoc.exe [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given:
  -IPATH, --proto_path=PATH   Specify the directory in which to search for
                              imports.  May be specified multiple times;
                              directories will be searched in order.  If not
                              given, the current working directory is used.
  --version                   Show version info and exit.
  -h, --help                  Show this text and exit.
  --encode=MESSAGE_TYPE       Read a text-format message of the given type
                              from standard input and write it in binary
                              to standard output.  The message type must
                              be defined in PROTO_FILES or their imports.
  --decode=MESSAGE_TYPE       Read a binary message of the given type from
                              standard input and write it in text format
                              to standard output.  The message type must
                              be defined in PROTO_FILES or their imports.
  --decode_raw                Read an arbitrary protocol message from
                              standard input and write the raw tag/value
                              pairs in text format to standard output.  No
                              PROTO_FILES should be given when using this
                              flag.
  -oFILE,                     Writes a FileDescriptorSet (a protocol buffer,
    --descriptor_set_out=FILE defined in descriptor.proto) containing all of
                              the input files to FILE.
  --include_imports           When using --descriptor_set_out, also include
                              all dependencies of the input files in the
                              set, so that the set is self-contained.
  --include_source_info       When using --descriptor_set_out, do not strip
                              SourceCodeInfo from the FileDescriptorProto.
                              This results in vastly larger descriptors that
                              include information about the original
                              location of each decl in the source file as
                              well as surrounding comments.
  --dependency_out=FILE       Write a dependency output file in the format
                              expected by make. This writes the transitive
                              set of input file paths to FILE
  --error_format=FORMAT       Set the format in which to print errors.
                              FORMAT may be 'gcc' (the default) or 'msvs'
                              (Microsoft Visual Studio format).
  --print_free_field_numbers  Print the free field numbers of the messages
                              defined in the given proto files. Groups share
                              the same field number space with the parent
                              message. Extension ranges are counted as
                              occupied fields numbers.

  --plugin=EXECUTABLE         Specifies a plugin executable to use.
                              Normally, protoc searches the PATH for
                              plugins, but you may specify additional
                              executables not in the path using this flag.
                              Additionally, EXECUTABLE may be of the form
                              NAME=PATH, in which case the given plugin name
                              is mapped to the given executable even if
                              the executable's own name differs.
  --cpp_out=OUT_DIR           Generate C++ header and source.
  --csharp_out=OUT_DIR        Generate C# source file.
  --java_out=OUT_DIR          Generate Java source file.
  --javanano_out=OUT_DIR      Generate Java Nano source file.
  --js_out=OUT_DIR            Generate JavaScript source.
  --objc_out=OUT_DIR          Generate Objective C header and source.
  --python_out=OUT_DIR        Generate Python source file.
  --ruby_out=OUT_DIR          Generate Ruby source file.

包——Packages

生成的類會放到基於java_package選項的Java包路徑下。如果java_package選項省略,package宣告會被使用。

例如,.proto檔案包含了:

package foo.bar;

生成的java檔案會被放到Java 包foo.bar中。然而,如果.proto檔案也包含了一個java_package選項,如下:

package foo.bar;
option java_package = "com.example.foo.bar";

java檔案會放到com.example.foo.bar包中。由於正常的.proto package宣告不能以一個倒序的域名作為開始,才會有一個java_package選項。

訊息——Messages

寫一個最簡單的訊息宣告:

message Foo {}

Protocol buffer 編譯器生成一個叫Foo的類,它實現了Message介面。這個類宣告為final;不允許有子類。Foo繼承自GeneratedMessage,但是它不是一個具體實現。預設情況下,為了效能最優,Foo覆寫了GeneratedMessage的很多方法。不過,如果.proto檔案包含了如下的行:

option optimize_for = CODE_SIZE;

這時,Foo僅會覆寫必要的一小部分方法,其餘方法則基於GeneratedMessage的反射實現。這樣顯著地減少了生成的程式碼的數量,但是也降低了效能。無獨有偶,如果.proto檔案包含了:

option optimize_for = LITE_RUNTIME;

那麼Foo會包含所有方法的最快實現,但是會實現MessageLite介面——僅僅包含了Message方法的子集。特別的,它不支援描述符或者反射。然而,在這種模式下,生成的程式碼只需要替換libprotobuf.jar連結到libprotobuf-lite.jar即可。“lite”庫比完整庫小得多,也更適合資源受限的系統比如手機端。

Message介面定義的方法讓你可以檢查,操作,讀寫整個訊息。除了這些方法,Foo類定義瞭如下的靜態方法:

  1. static Foo getDefaultInstance(): 返回Foo的一個單例,如果你呼叫Foo.newBuilder().build(),其結果是一樣的 (因此所有的單個的欄位都是未賦值的,所有的重複欄位都是空的)。請注意,一個訊息的預設例項可以被當做一個工廠,可以呼叫它(例項物件)的newBuilderForType()方法。

    default instance

  2. static Descriptor getDescriptor(): 返回訊息型別的描述器。它包含型別的資訊,包括它有哪些欄位和欄位型別。可以通過呼叫Message的反射方法,比如getField()。這個返回的descriptor是HelloWorldProto類的一個例項。

    descriptor

  3. static Foo Foo parseFrom(...): 從給的資源解析訊息型別Foo並返回。在Message.Builder介面中都有一個parseFrom方法對應於一個mergeFrom。請注意,parseFrom() 從不丟擲UninitializedMessageException;它丟擲InvalidProtocolBufferException,如果解析的訊息丟失要求的欄位。這使得它和呼叫Foo.newBuilder().mergeFrom(...).build()有細微的差別。

    parseFrom

    parsefrom2

  4. static Parser parser(): 返回一個Parser的例項,它實現了各種parseFrom()介面。

    parser

  5. Foo.Builder newBuilder(): 建立一個新的builder

    newbuilder

  6. Foo.Builder newBuilder(Foo prototype): 使用和prototype的同樣的欄位值來建立一個新的builder。由於內嵌的訊息和字元物件是不可變的,他們在原始和副本之間共享。
    見上圖

構造器——Builders

builder

訊息物件——比如上述Foo類的例項——是不可變的,就像Java中的String。要構造一個訊息物件,你需要使用一個構造器。每個訊息類都有他自己的構造類——因此在你的Foo例子中,protocol buffer編譯器生成了一個內嵌的Foo.Builder類,它可被用來構造一個Foo物件。Foo.Builder實現了Message.Builder 介面。它繼承了GeneratedMessage.Builder類,但是,再次強調,它不是一個具體實現。和Foo一樣,Foo.Builder 可能會依賴GeneratedMessage.Builder中的通用方法實現,當optimize_for選項使用時,生成的自定義程式碼會更快。

Foo.Builder不會定義任何靜態方法。它的介面完全由Message.Builder介面定義,不同的是,返回型別更具體:Foo.Builder中修改builder的方法返回型別為Foo.Builder,而build()返回型別Foo。

修改builder內容的方法——包括欄位的setter方法——總是返回一個builder的引用(例如它們 “return this;”)。這允許多個方法在一行中進行鏈式呼叫。比如:

builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();

子構造器——Sub Builders

由於訊息包含子訊息,編譯器也可以生成子構造器。這允許你重複修改深度內嵌的子訊息而不用重新構造他們。例如:

message Foo {
  optional int32 val = 1;
  // some other fields.
}

message Bar {
  optional Foo foo = 1;
  // some other fields.
}

message Baz {
  optional Bar bar = 1;
  // some other fields.
}

如果你已經有了Baz訊息,然後想改變深層內嵌的Foo中val。取代下邊這種寫法:

baz = baz.toBuilder().setBar(
      baz.getBar().toBuilder().setFoo(
          baz.getBar().getFoo().toBuilder().setVal(10).build()
      ).build()).build();

你可以這樣寫:

  Baz.Builder builder = baz.toBuilder();
  builder.getBarBuilder().getFooBuilder().setVal(10);
  baz = builder.build();

內嵌型別——Nested Types

一個訊息可以被宣告在另外一個訊息內部。比如:

message Foo { message Bar { } }

這種情況下,編譯器會簡單生成Bars作為內嵌在Foo中的內部類

欄位——Fields

除了上一部分描述的方法,protocol buffer編譯器為每個定義在.proto檔案的message中的欄位生成一系列訪問的方法。這些方法讀取message類和它響應的builder中都會定義的欄位值;這些方法修改只修改定義在builder中欄位值。
注意:這些方法名字總是使用駝峰命名規則,即使在.proto檔案中的欄位名使用小寫和下劃線(_)組合。大小寫轉換流程如下:

  1. 對於名字中的每個下劃線,下劃線被幹掉,後續的字母使用大寫。
  2. 如果名字有一個字首(比如”get”),首字母大寫,否則小寫。

這樣,欄位foo.bar_baz變成fooBarBaz。如果字首是get,它會變成getFooBarBaz。

和訪問方法一樣,編譯器為每一個欄位(包括欄位標識)生成一個整形常量。常量名字是欄位名轉換成大寫後邊跟一個_FIELD_NUMBER。例如, 給定一個欄位可選的int32 foo_bar = 5;,編譯器會生成常量

public static final int FOO_BAR_FIELD_NUMBER = 5;.

單個欄位——Singular Fields (proto3)

對於一個欄位定義:

int32 foo = 1;

編譯器會同時在訊息類和它的builder中生成如下的訪問方法:

int getFoo():  返回欄位的當前值。如果欄位未設值,返回這個欄位型別的預設值。

編譯器只會在message的builder中生成如下的方法:

  1. Builder setFoo(int value): 設定欄位的值。呼叫之後,hasFoo() 將會返回truegetFoo() 將會返回value
  2. Builder clearFoo(): 清除欄位的值。呼叫之後,getFoo()將會返回欄位型別的預設值。

    對於其他簡單欄位型別,相應的Java型別會根據scalar value表進行選擇。對於訊息和列舉型別,其值型別會被訊息或者列舉型別的類替換掉。

內嵌訊息欄位——Embedded Message Fields

對於訊息欄位型別,setFoo() 也接受一個訊息構造器型別的例項作為引數。這僅是構造器的.build()呼叫的快捷方式,結果回傳給setFoo()方法。

如果欄位未設值,getFoo() 會返回一個帶有欄位集合的Foo例項(可能會是由Foo.getDefaultInstance()返回的例項)。

另外,編譯器生成兩個訪問器方法,允許你訪問相關的訊息的子構建器。下列方法由訊息類和它的構造器生成:

  1. FooOrBuilder getFooOrBuilder(): 返回欄位的構造器,如果它已存在,或者訊息不存在。

編譯器生成僅在訊息的構造器中生成如下的方法:

  1. Builder getFooBuilder(): 返回欄位的構造器。

列舉欄位——Enum Fields

對於列舉欄位型別,一個額外的訪問器方法會由訊息類和它的構造器生成:

  1. int getFooValue(): 返回列舉的整型值

編譯器僅會在訊息的構造器中生成如下的額外方法:

  1. Builder setFooValue(int value): 設定列舉的整形值。

另外,getFoo()會返回UNRECOGNIZED如果列舉型別是未知的——這是一個由proto3編譯器為生成的列舉類相關新增的特殊額外的值。

重複欄位——Repeated Fields

對於這個欄位定義:

repeated int32 foo = 1;

編譯器會同時在message類和它的builder中生成如下的訪問方法:

  1. int getFooCount(): 返回當前欄位中的元素個數。
  2. int getFoo(int index): 返回0為起點的索引處的元素
  3. List getFooList(): 以列表的形式返回整個欄位。如果欄位未設定,返回空的列表。對於message類和返回的列表是不可變的(immutable),對於message builer類,是不可修改的(unmodifiable)。

編譯器會在message的builder中生成如下的方法:

  1. Builder setFoo(int index, int value): 設定以0起始的索引處的元素的值
  2. Builder addFoo(int value): 用給定的值為子彈追加新元素。
  3. Builder addAllFoo(Iterable<? extends Integer> value): 使用給定的Iterable追加新元素到欄位中。
  4. Builder clearFoo(): 從field中刪除所有元素。此方法呼叫後,getFooCount() 會返回零。

對於其他簡單的欄位型別,相應的Java型別會根據scalar 值型別表進行選擇。對於訊息和列舉型別,型別是訊息或者列舉類。

重複內嵌欄位——Repeated Embedded Message Fields

對於訊息型別,setFoo() and addFoo()也會接受一個訊息的builder 型別作為引數。這僅是builder的.build()呼叫的快捷方式,結果回傳給呼叫方法。

另外,編譯器會在messge類和它的builder中,生成如下的額外訪問器方法,允許你訪問相關的子構造器:

  1. FooOrBuilder getFooOrBuilder(int index): 返回指定元素的builder,如果它已經存在,或者元素不存在。如果這是由一個message類呼叫,它會總是返回一個訊息而不是一個builder。
  2. List<FooOrBuilder> getFooOrBuilderList(): 以一個不可修改(unmodifiable)列表的形式返回builder(如果可用)或者message(如果builder不可用)的整個欄位。如果這個是由一個message類來呼叫的,它會總是返回一個訊息的不可變列表而不是builder是的一個不可修改列表。

編譯器僅會在訊息的builder中生成如下的方法:

  1. Builder getFooBuilder(int index): 返回指定索引處元素的builder。

  2. Builder addFooBuilder(int index): 在指定索引處追加一個預設訊息例項的builder並返回

  3. Builder addFooBuilder(): 追加一個預設訊息例項的builder。

  4. Builder removeFoo(int index): 刪除以0為起始的索引處的元素

  5. List<Builder> getFooBuilderList(): 以一個不可修改的builder的列表返回整個欄位。

重複列舉型別——Repeated Enum Fields (僅proto3 )

編譯器會同時在message類和它的builder中生成如下額外的方法:

  1. int getFooValue(int index): 返回指定索引處的列舉型別的值。
  2. List getFooValueList(): 以整數列表的形式返回整個欄位。

編譯器僅會在訊息builder中生成如下額外的方法:

Builder setFooValue(int index, int value): 為指定索引出的列舉型別設定整形變數

Oneof 欄位

對於oneof欄位的定義:

oneof oneof_name {
    int32 foo = 1;
    ...
}

編譯器會同時在message類和它的builder中生成如下額外的方法:

  1. boolean hasFoo() (proto2 only): 如果oneof case是FOO,返回true
  2. int getFoo(): 如果oneof case 是FOO則返回oneof_name的當前值。否則返回這個欄位的預設值。

編譯器僅會在訊息builder中生成如下額外的方法:

  1. Builder setFoo(int value): 設定oneof_name為給定值,並設定oneof case為FOO。當呼叫這個方法的時候,hasFoo()將會返回true,getFoo()將會返回value代表的值,getOneofNameCase()返回FOO。

  2. Builder clearFoo(): 如果oneof case不是FOO,不改變任何東西。如果oneof case是FOO,設定oneof_name為null,oneof case 為ONEOF_NAME_NOT_SET。呼叫之後, hasFoo()會返回false,getFoo() will將會返回預設值,getOneofNameCase()將會返回ONEOF_NAME_NOT_SET

對於其他簡單欄位型別,相應的java型別根據scalar 值型別表進行選擇。 對於訊息和列舉型別,值型別會被message或者enum類替換。

Map 欄位

對於map型別欄位的定義:

map<int32, int32> weight = 1;

編譯器會同時在message類和它的builder中生成如下額外的方法:

  1. Map<Integer, Integer> getWeightMap(): 返回不可修改的Map。

  2. int getWeightOrDefault(int key, int default): 返回給定的key對應的value,如果不存在,則返回預設值。

  3. int getWeightOrThrow(int key): 返回給定的key對應的value,如果不存在,則丟擲異常IllegalArgumentException

  4. boolean containsWeight(int key): 指出field中給定的key是否存在。

  5. int getWeightCount(): 返回map中元素的個數。

編譯器僅會在訊息builder中生成如下額外的方法:

  1. Builder putWeight(int key, int value): 新增權重到這個欄位。

  2. Builder putAllWeight(Map<Integer, Integer> value): 新增給定map中所有的entries到這個欄位中。

  3. Builder removeWeight(int key): 從這個欄位中刪除權重。

  4. Builder clearWeight(): 從這個欄位中刪除所有權重。

  5. @Deprecated Map<Integer, Integer> getMutableWeight(): 返回一個可變的map. 注意,多次呼叫這個方法可能返回不同的map例項。返回的map引用可能因任何後續的到Builder的方法呼叫而失效。

Any

Any 欄位定義像這樣:

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

在我們生成的程式碼中,具體欄位的getter方法返回一個com.google.protobuf.Any的例項。提供瞭如下的pack和unpackAny型別值的特殊方法:

class Any {
  // Packs the given message into an Any using the default type URL
  // prefix “type.googleapis.com”.
  public static Any pack(Message message);
  // Packs the given message into an Any using the given type URL
  // prefix.
  public static Any pack(Message message,
                         String typeUrlPrefix);

  // Checks whether this Any message’s payload is the given type.
  public <T extends Message> boolean is(class<T> clazz);

  // Unpacks Any into the given message type. Throws exception if
  // the type doesn’t match or parsing the payload has failed.
  public <T extends Message> T unpack(class<T> clazz)
      throws InvalidProtocolBufferException;
}

Oneofs

oneofs定義:

oneof oneof_name {
    int32 foo_int = 4;
    string foo_string = 9;
    ...
}

所有在oneof_name oneof中的欄位將會使用共享的oneof_name物件作為它們的值。另外,protocol buffer編譯器會為oneof case生成Java列舉型別,如下:

public enum OneofNameCase
        implements com.google.protobuf.Internal.EnumLite {
      FOO_INT(4),
      FOO_STRING(9),
      ...
      ONEOF_NAME_NOT_SET(0);
      ...
    };

列舉型別的值會有如下的特殊方法:

  1. int getNumber(): 返回與.proto檔案中定義一樣的物件的數值型別的值

  2. static OneofNameCase forNumber(int value):根據給定的數值返回列舉物件。

編譯器會同時在message類和它的builder中生成如下額外的方法:

  1. OneofNameCase getOneofNameCase(): 返回列舉指明哪個欄位是集合。如果他們都都不是集合,返回ONEOF_NAME_NOT_SET。

編譯器僅會在訊息builder中生成如下額外的方法:

  1. Builder clearOneofName(): 設定oneof_name為null,設定oneof case為ONEOF_NAME_NOT_SET。

列舉

列舉定義:

enum Foo {
  VALUE_A = 0;
  VALUE_B = 5;
  VALUE_C = 1234;
}

服務——Services

如果.proto檔案包含如下行:

option java_generic_services = true;

那麼,protocol buffer 編譯器會根據在.proto中定義的服務生成程式碼。然而,生成的程式碼可能會不好用,因為它它未繫結到任何特定的RCP系統,這樣的話,繫結到一個RPC系統會需要更多層次的間接的程式碼調整。如果你不想生成程式碼,把這行新增到.proto檔案中:

option java_generic_services = false;

如果上邊的語句未給出,這個選項預設是false,因為通用服務已經過時。(注: 在2.4.0版本之前, 這個選項預設是true)

基於.proto-語言服務定義的RPC系統,應該提供一個外掛用來生成適用於該系統的程式碼。這些外掛會要求抽象服務是disabled,這樣他們才能生成他們自己同名的類。外掛在2.3.0 (January 2010)版本中是新的。

剩下的章節描述了當抽象服務是enable的情況下protocol buffer編譯器會生成什麼東西。

介面——Interface

給一個服務定義:

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

protocol buffer編譯器會生成抽象類Foo來表示這個服務。對於定義在服務中的每個方法,Foo類都會有一個抽象方法與之對應。這種情況下,Bar方法是這樣定義的:

abstract void bar(RpcController controller, FooRequest                request, RpcCallback<FooResponse> done);

引數和 Service.CallMethod()的引數是等同的,除了方法引數是隱式,requestdone都明確地指定了型別。

Foo實現了Service 介面。Protocol buffer 編譯器自動生成如下介面方法的實現:

  1. getDescriptorForType: 返回服務的ServiceDescriptor

  2. callMethod: 基於提供的方法描述符決定哪個方式被呼叫,然後直接呼叫該方法,向下轉型請求訊息和回撥為正確的型別。

  3. getRequestPrototype and getRequestPrototype: 為給定的方法返回正確型別的請求或者響應的預設例項

如下的靜態方法也被生成:
static ServiceDescriptor getDescriptor(): 返回這個型別的描述符,它包含了關於這個服務中有哪些方法以及方法的輸入和輸出型別等資訊。

Foo 還包含了一個內嵌的介面Foo.Interface。這個是一個純介面,再一次包含了定義於你的服務中的每個方法。然而,這個介面不繼承自Service 介面。這個問題是由於RPC伺服器實現,通常會用抽象服務的物件寫,而不是你特定服務。為了解決這個問題,如果你有一個object impl實現了Foo.Interface,你可以呼叫Foo.newReflectiveService(impl)
來構建一個作為impl的簡單代理的Foo類的例項,並且實現服務。

總括來說, 當實現你自己的服務的時候,你有兩個選項:

  1. 繼承Foo,然後視情況而定,實現它的方法,然後持有直接到RPC伺服器實現的子類的例項。這通常很簡單,但是有些人認為它不夠“純淨”。

  2. 實現Foo.Interface介面,使用Foo.newReflectiveService(Foo.Interface)來構造一個Service wrapping,然後傳遞這個wrapper 到你的RPC實現。

存根——Stub

Protocol buffer編譯器也會為每一個服務介面生成“存根”的實現,它被客戶機用來發送請求服務端。對於Foo service (上述),存根實現Foo.Stub會被定義為內部類。
Foo.Stub 是Foo的一個子類,也實現瞭如下的方法:

  1. Foo.Stub(RpcChannel channel): 構造一個通過給定的通道傳送請求的新的stub。
  2. RpcChannel getChannel(): 返回傳遞給建構函式的當前stub的通道。

Stub包裝了通道,實現了每個service定義的方法,對每個方法的呼叫,最終都是對channel.callMethod()的簡單呼叫。

Protocol Buffer 類庫不包含RPC的實現。然而,它包含了所有你需要的工具,連線一個生成的服務類到任意RPC實現。你只需要提供RpcChannelRpcController的實現。

阻塞介面——Blocking Interfaces

上述的RPC類都有非阻塞(non-blocking )語義:當你呼叫一個方法的時候,提供一個方法完成時呼叫的回撥物件。通常編寫阻塞(blocking)語義的程式碼更容易一些(即便擴充套件性差一些),方法直到完成時才會返回。
為了滿足上述要求,Protocol buffer編譯器也生成了服務的阻塞版本。Foo.BlockingInterface 等同於Foo.Interface,除非每個方法不是使用回撥而只是簡單地返回結果。那麼,舉個例子,bar被定義為:

abstract FooResponse bar(RpcController controller, FooRequest request) throws ServiceException;

和非阻塞服務相似,Foo.newReflectiveBlockingService(Foo.BlockingInterface) 返回一個阻塞的服務,它包裝了Foo.BlockingInterface。最終,Foo.BlockingStub返回一個Foo.BlockingInterface的stub實現,這個介面會發送請求到特定的BlockingRpcChannel(阻塞通道)。

外掛插入點——Plugin Insertion Points

程式碼生成外掛為了繼承Java程式碼生成器的輸出,可能會使用給定的插入點的名字插入如下型別的程式碼:

  1. outer_class_scope:屬於外部類成員宣告。

  2. class_scope:TYPENAME: 屬於訊息類的成員宣告。TYPENAME 是完整的proto名字,比如包名.訊息型別——package.MessageType

  3. builder_scope:TYPENAME:屬於訊息構造器類的成員宣告。TYPENAME是完整的proto名字,比如包名.訊息型別——package.MessageType

  4. enum_scope:TYPENAME: 屬於列舉類的成員宣告。TYPENAME是完整的proto 列舉 名字, 比如包名.列舉型別——package.EnumType

生成的程式碼不能包含import語句,因為它們有可能和定義在生成程式碼中的型別名字衝突。當指向一個外部類的時候,你必須總是是用全路徑名稱。

注意:對於Java程式碼生成器,決定輸出檔名稱的邏輯非常複雜。你可能需要檢視protoc的原始碼,特別是java_headers.cc,確保你考慮到了所有的情形。
注意:不要依賴標準程式碼生成器宣告的私有類屬性來生成程式碼,因為這些實現細節可能在未來的Protocol Buffers的版本中會有改動。