本文描述了protocol buffers使用.proto文件生成pb.go文件的過程
編譯器
編譯器需要插件來編譯環境,使用如下方式安裝插件:go get github.com/golang/protobuf/protoc-gen-go
使用.proto生成的文件相比輸入文件有如下兩處變更:
-
- 生成文件的文件名為:輸入文件的擴展名.pb.go,如使用player.proto生成的文件名為player.pb.go
- 生成文件的路徑為--go_out指定的文件
當執行如下命令時:
protoc --proto_path=src --go_out=build/gen src/foo.proto src/bar/baz.proto
編譯器會讀取src/foo.proto src/bar/baz.proto,並分別生成build/gen/foo.pb.go and build/gen/bar/baz.pb.go。編譯器會自動生成build/gen/bar目錄,但不會生成build或build/gen目錄。
包
如果.proto文件包含包定義,則生成的代碼會使用.proto的package,與go的package處理類似,會將package名字中的"."轉換為"_"。如proto package名為example.high_score,對應生成的代碼的package name為example_high_score。
使用go_package選項可以替換默認條件下.proto生成的package name。如下生成的go package為“hs".
package example.high_score; option go_package = "hs";
如果.proto文件中沒有包含package聲明,則生成的代碼會使用文件名(處理方式類似go package name)
消息
下面是一個簡單的message
message Foo {}
protocol buffer 編譯器會生成一個struct,名為Foo。A *Foo實現了該接口的方法。下述成員會出現在所有message生成的go代碼中
type Foo struct { } // Reset sets the proto's state to default values. func (m *Foo) Reset() { *m = Foo{} } // String returns a string representation of the proto. func (m *Foo) String() string { return proto.CompactTextString(m) } // ProtoMessage acts as a tag to make sure no one accidentally implements the // proto.Message interface. func (*Foo) ProtoMessage() {}
內嵌類型
如下內嵌場景下會生成2個獨立的struct,Foo和Foo_Bar
message Foo { message Bar { } }
Well_known 類型
protocol buffer的預定義消息集合,稱為well_known types(WKTs)。這些類型在與其他服務交互時比較好用。如Struct消息表示了任意的C風格的struct。
為WTKs預生成的go代碼作為Go protobuf library的一部分發布。如給出一個message
import "google/protobuf/struct.proto" import "google/protobuf/timestamp.proto" message NamedStruct { string name = 1; google.protobuf.Struct definition = 2; google.protobuf.Timestamp last_modified = 3; }
生成的Go代碼如下:
import google_protobuf "github.com/golang/protobuf/ptypes/struct" import google_protobuf1 "github.com/golang/protobuf/ptypes/timestamp" ... type NamedStruct struct { Name string Definition *google_protobuf.Struct LastModified *google_protobuf1.Timestamp }
字段
生成的go字段名稱遵循駝峰命名法,規則如下:
-
- 首字母大寫,如果首字符是下划線,則使用大寫X替換該下划線
- 如果字符內部的下划線后跟着小寫的字母,則移除該下划線,並將原來下划線后面的字母大寫
如foo_bar_baz變為FooBarBaz,_my_field_name_2變為XMyFieldName_2
Singular Scalar Fields (proto3)
int32 foo = 1;
編譯器會生成一個包含名為int32字段,名為Foo的struct,以及一個名為GetFoo()的方法,該方法會返回Foo中定義的int32的值,或默認值(如果設置初始值)
Singular Message Fields
message Bar {}
message Baz {
Bar foo = 1;
}
針對message Baz,編譯器會生成如下struct,以及一個func (m *Baz)GetFoo() *Bar的函數。
type Baz struct { Foo *Bar //結構體使用指針 }
Repeated Fields
message Baz { repeated Bar foo = 1; }
生成如下struct。類似地,如果字段定義為 repeated bytes foo = 1,編譯器會生成名為Foo,含[][]byte字段的Go struct;字段定義為 repeated MyEnum bar = 2,則會生成名為Bar,包含[]MyEnum字段的struct
type Baz struct { Foo []*Bar //相比不帶repead的,多了"[]" }
Map Fields
message Bar {} message Baz { map<string, Bar> foo = 1; }
編譯器生成如下struct
type Baz struct{ Foo map[string]*Bar //map中的結構體也是指針表達方式 }
Oneof Fields
針對oneof字段,protobuf編譯器會生成接口類型 isMessageName_MyField。此外oneof中的每個singular字段會生成struct,isMessageName_MyField接口。如下oneof:
package account; message Profile { oneof avatar { string image_url = 1; bytes image_data = 2; } }
編譯器會生成struct:
type Profile struct { // Types that are valid to be assigned to Avatar: // *Profile_ImageUrl // *Profile_ImageData Avatar isProfile_Avatar `protobuf_oneof:"avatar"` } type Profile_ImageUrl struct { ImageUrl string } type Profile_ImageData struct { ImageData []byte }
*Profile_ImageUrl 和*Profile_ImageData都使用一個空的isProfile_Avatar()實現了isProfile_Avatar 編譯器同時會生成func (m *Profile) GetImageUrl() string 和func (m *Profile) GetImageData() []byte
如下展示了如何設置字段:
p1 := &account.Profile{ Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"}, } // imageData is []byte imageData := getImageData() p2 := &account.Profile{ Avatar: &account.Profile_ImageData{imageData}, }
可以使用如下來處理不同的message類型
switch x := m.Avatar.(type) { case *account.Profile_ImageUrl: // Load profile image based on URL // using x.ImageUrl case *account.Profile_ImageData: // Load profile image based on bytes // using x.ImageData case nil: // The field is not set. default: return fmt.Errorf("Profile.Avatar has unexpected type %T", x) }
Enumerations
message SearchRequest { enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 1; ... }
protocol buffer會生成一個類型以及一系列該類型表示的常量。在message內部的enums,type的名稱會以message名稱開頭:
type SearchRequest_Corpus int32
const (
SearchRequest_UNIVERSAL SearchRequest_Corpus = 0
SearchRequest_WEB SearchRequest_Corpus = 1
SearchRequest_IMAGES SearchRequest_Corpus = 2
SearchRequest_LOCAL SearchRequest_Corpus = 3
SearchRequest_NEWS SearchRequest_Corpus = 4
SearchRequest_PRODUCTS SearchRequest_Corpus = 5
SearchRequest_VIDEO SearchRequest_Corpus = 6
)
package級別的enum
enum Foo { DEFAULT_BAR = 0; BAR_BELLS = 1; BAR_B_CUE = 2; }
Go類型以原來的enum,該類型還有一個String()方法來返回給定值的名字,Enum()方法初始化並分配給定值的內存,返回相應的指針。
type Foo int32
func (Foo) Enum() *Foo
protocol buffer編譯器也會整數到字符串名稱以及名稱到數值的對應關系
var Foo_name = map[int32]string{ 0: "DEFAULT_BAR", 1: "BAR_BELLS", 2: "BAR_B_CUE", } var Foo_value = map[string]int32{ "DEFAULT_BAR": 0, "BAR_BELLS": 1, "BAR_B_CUE": 2, }
.proto允許多enum的數值相同。由於多名稱對應一個數值,逆向對應關系則是數值與.proto文件中出現的第一個名稱相對應(一個對應關系)。
service
Go代碼生成器默認不會為services生成代碼。如果使能了gRPC插件,則可以支持個RPC代碼的生成。