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才会被编译输出。