Go generated code (protobuf)
.proto文件中所有的名字(例如message名,字段名),编译后生成go文件后都是驼峰式的名字,名字转换规则都是一样的。
例如message名为Test,test编译后都是Test
Messages
假设有message的定义为:
message Foo{
string hello=1;
}
则编译后的go文件会对应生成有类型名为Foo的结构体,以及以*Foo指针接收者的方式实现了proto.Message接口,该接口有一个ProtoReflect方法,返回protoreflect.Message,提供反射角度下的message信息。
嵌套类型 nested types
假设message定义为
message Foo{
message Bar{
}
}
则编译后的go文件有两种结构体类型,分别叫做Foo和FooBar
例子:
message Test{
int32 i1=1;
enum NUMBER{
DEFAULT=0;
ACTIVATION=1;
SHUTDOWN=2;
}
NUMBER num=2;
message inner {
string FirstName=1;
string LastName=2;
}
inner inner_msg=3;
}
编译出来:
type Test struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
I1 int32 `protobuf:"varint,1,opt,name=i1,proto3" json:"i1,omitempty"`
Num Test_NUMBER `protobuf:"varint,2,opt,name=num,proto3,enum=Test_NUMBER" json:"num,omitempty"`
InnerMsg *TestInner `protobuf:"bytes,3,opt,name=inner_msg,json=innerMsg,proto3" json:"inner_msg,omitempty"`
}
type TestInner struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FirstName string `protobuf:"bytes,1,opt,name=FirstName,proto3" json:"FirstName,omitempty"`
LastName string `protobuf:"bytes,2,opt,name=LastName,proto3" json:"LastName,omitempty"`
}
字段
message中的每个字段都会被编译成go结构体中的字段,在pb中使用的下划线和小写组合的字段名(都应该这么写),在编译后变成驼峰式的字段名。
规则如下:
1.如果pb中字段名的第一个字符是下划线,该下划线会被去掉,并改成X。
2.如果pb中字段名中间出现了下划线,下划线都会被去掉,并且下划线的下一个字符将变成大写。
例子:
pb中: foo_bar_baz ==> go中:FooBarBaz
pb中:_hello_world ==> go中:XHelloWorld
对于singular字段
message Test{
int32 foo=1;
}
编译器除了生成一个Test结构体类型,且该结构体含有名为Foo的字段外,还会提供访问的API,GetFoo(),如果有该字段的信息,则会返回一个int32的值,如果没有则返回零值。其他类型的singular字段同理,如果没有该字段的信息则返回的是相应类型的零值。
对于singular的message字段,编译后是message对应的结构的指针。
message Bar{}
message Baz{
Bar foo=1;
}
编译后得到的是:
type Baz struct{
Foo *Bar
}
可以看到编译后的Foo字段是个指向Bar类型的指针,这样当没有该字段时,赋值为nil。类似地,编译器也提供了GetFoo()的API, func (m *Baz) GetFoo() *Bar ,当m为nil或foo没有给定时,均返回nil的*Bar指针。
对于repeated字段
每个repeated字段编译后都对应一个该字段类型的切片
message Baz{
repeated Bar foo =1;
}
编译后:
type Baz struct{
Foo []*Bar
}
Likewise, for the field definition repeated bytes foo = 1; the compiler will generate a Go struct with a [][]byte field named Foo. For a repeated enumeration repeated MyEnum bar = 2;, the compiler generates a struct with a []MyEnum field called Bar.
对于每个字段而言,编译后相应的都有Getter和Setter方法,只是要留一下这些函数的返回值类型是值还是引用。
对于上面的结构体,初始化和获取的方式:
baz:=&Baz{
Foo:[]*Bar{{},{}} //初始化Foo字段有两个空的Bar元素
}
foo:=baz.GetFoo() //foo type is []*Bar
f1=foo[0] //f1类型为*Bar,是foo的第一个元素
map字段
这个很好理解,pb中的map字段类型,编译后为go结构体中的一个map字段
message Bar{}
message Baz{
map<string,Bar> foo=1;
}
编译后
type Baz struct{
Foo map[string]*Bar
}
oneof字段
因为oneof字段是其中某个字段之一,所以在编译后,该字段在结构体中是一个接口类型,接口的类型名为isMessageName_FieldName,oneof中的所有字段则会单独编译成一个结构体(结构体的类型名格式MessageName_FieldName),这些结构体都实现了isMessageName_FieldName接口,因此oneof中的字段就可以赋给oneof字段。
说起来比较拗口,看例子秒懂:
message Profile{
oneof avatar{
string image_url=1;
bytes image_data=2;
}
}
编译后
type Profile struct{
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(),两种结构体都实现了该空方法。
因此,在go代码中的使用方法:
p1:=&Profile{Avatar:&Profile_ImageUrl{ImageUrl:"www.baidu.com"}}
//assume data is []byte
data:=getImageData()
p2:=&Profile{Avatar:&Profile_ImageData{ImagData:data}}
注意由于是以指针接收者的方式实现了oneof的接口,所以上述代码在指定Avatar字段时都用了指针。
编译器会提供oneof中每个字段的getter方法。
编译器相应的也提供了profile对象获取oneof字段对象的方法,以上述为例,提供了func(m *Profile) GetImageUrl() string和func(m *Profile) GetImageData() []byte的方法。根据oneof字段的特性,如果恰好oneof字段的类型就是某个类型A,则GetA返回A的值,否则其他情况下GetX都返回X的零值。
具体来说,如果当前Profile的Avatar存的是&Profile_ImageData,则GetImageData返回data数据,而如果此时调用GetImageUrl,则得到的是一个空字符串。
枚举型字段
message SearchRequest{
enum Corpus{
UNIVERSAL=0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus=1;
}
编译后
type SearchRequest struct{
Corpus SearchRequest_Corpus
}
type SearchRequest_Corpus int32
const(
SearchRequest_Corpus_UNIVERSAL=0
SearchRequest_Corpus_WEB=1
SearchRequest_Corpus_IMAGES=2
SearchRequest_Corpus_LOCAL=3
SearchRequest_Corpus_NEWS=4
SearchRequest_Corpus_PRODUCTS=5
SearchRequest_Corpus_VIDEO=6
)
func(s SearchRequest_Corpus) String() string //根据s的取值,返回相应的enum的名字,相当于从数字映射回enum名
func(SearchRequest_Corpus) Enum() *SearchRequest_Corpus//根据当前SearchRequest_Corpus的值,分配新的内存,返回一个新的SearchRequest_Corpus的指针
同时,编译器还会编译从枚举值到枚举名的map,和枚举名到枚举值的map:
var SearchRequest_Corpus_name=map[int32]string{
0:"UNIVERSAL",1:"WEB",2:"IMAGES",3:"LOCAL",4:"NEWS",5:"PRODUCTS",6:"VIDEO"
}
var SearchRequest_Corpus_value=map[string]int32{
"UNIVERSAL":0,"WEB":1,"IMAGES":2,"LOCAL":3,"NEWS":4,"PRODUCTS":5,"VIDEO":6
}
这两个map分别以name和value为后缀
services
默认情况下,编译器不会编译services的输出,如果开启了gRPC的插件,这些service才会被编译输出。