声明:版权所有,谢绝转载。
在go中使用google protobuf,有两个可选用的包: goprotobuf(go官方出品)和gogoprotobuf(gogo组织出品^_^)。
gogoprotobuf能够完全兼容google protobuf。而且经过我一些测试,它生成大代码质量确实要比goprotobuf高一些,主要是它在goprotobuf之上extend了一些option。这些option也是有级别区分的,有的option只能修饰field,有的可以修饰enum,有的可以修饰message,有的是修饰package(即对整个文件都有效)。下面一一说明其一些选项的意义。
另外,本文的所有proto示例都是proto v3格式。
1 gogoproto.goproto_enum_prefix
如果选项为false,则生成的代码中不加"E_"。
pb code: enum E { // option (gogoproto.goproto_enum_prefix) = false; A = 0; B = 2; } go code: const ( // option (gogoproto.goproto_enum_prefix) = false; E_A E = 0 E_B E = 2 ) or pb code: enum E { // option (gogoproto.goproto_enum_prefix) = false; A = 0; B = 2; } go code: const ( A E = 0 B E = 2 )
2 gogoproto.goproto_getters
如果选项为false,则不会为message的每个field生成一个Get函数。
pb code: message test { // option (gogoproto.goproto_getters) = false; E e = 1; } go code: type Test struct { E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test) GetE() E { if m != nil && m.E != nil { return *m.E } return A } or pb code: message test { option (gogoproto.goproto_getters) = false; E e = 1; } go code: type Test struct { E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"` XXX_unrecognized []byte `json:"-"` }
3 gogoproto.face
当这个选项为true的时候,会为message生成相应的interface。
message test { option (gogoproto.goproto_getters) = false; option (gogoproto.face) = true; string msg = 1; } type Test struct { Msg *string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } type TestFace interface { Proto() github_com_gogo_protobuf_proto.Message GetMsg() *string } func (this *Test) Proto() github_com_gogo_protobuf_proto.Message { return this } func (this *Test) TestProto() github_com_gogo_protobuf_proto.Message { return NewTestFromFace(this) } func (this *Test) GetMsg() *string { return this.Msg } func NewTestFromFace(that TestFace) *Test { this := &Test{} this.Msg = that.GetMsg() return this }
这个选项要求goproto_getters选项为false,即只生成interface相应的method。否则,你会收到如下error:
panic: face requires getters to be disabled please use gogoproto.getters or gogoproto.getters_all and set it to false goroutine 1 [running]: github.com/gogo/protobuf/plugin/face.(*plugin).Generate(0x2086c00a0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/plugin/face/face.go:164 +0x37d github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).runPlugins(0x2087001c0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1008 +0x91 github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).generate(0x2087001c0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1047 +0x3e1 github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).GenerateAllFiles(0x2087001c0) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:994 +0x249 main.main() /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/main.go:96 +0x31d --gogo_out: protoc-gen-gogo: Plugin failed with status code 2.
4 gogoproto.nullable
有没有注意到上面的示例中Test的成员msg类型为string*,当你要向它赋值的时候,可能要写出如下代码:
var test Test test.Msg = new(string) *test.Msg = "test.msg" or test := Test{Msg:proto.String("hello")}
是不是感觉很麻烦?而且生成一堆临时对象,不利于gc。此时如果nullable选项为false,就不用这么麻烦了
pb code: message test { string msg = 1 [(gogoproto.nullable) = false]; } go code: type Test struct { Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` }
严格地说,nullable这个option是违背protobuf的初衷的。使用了它之后,message序列化的时候,gogo会为message的每个field设置一个值,而google protobuf则是要求如果一个option的field没有被赋值,则序列化的时候不会把这个成员序列化进最终结果的。gogo官方的详尽解释是:
The protocol buffer specification states, somewhere, that you should be able to tell whether a field is set or unset. With the option nullable=false this feature is lost, since your non-nullable fields will always be set. It can be seen as a layer on top of protocol buffers, where before and after marshalling all non-nullable fields are set and they cannot be unset.
摘自:https://godoc.org/code.google.com/p/gogoprotobuf/gogoproto
5 gogoproto.customname
在生成的代码中修改成员的名称,这个选项在这种情况下非常有用:field的名称与message的method的名称一样。
pb code: message test { option (gogoproto.goproto_getters) = false; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` }
类似的选项还有gogoproto.customtype,不再赘述。
6 gogoproto.goproto_stringer
此选项不设置的时候,其为true。当这个选项为false的时候,gogo不再为message对一个的struct生成String()方法。这个选项建议不要设置为false。
pb code: message test { option (gogoproto.goproto_stringer) = true; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} or pb code: option (gogoproto.goproto_getters_all) = false; message test { option (gogoproto.goproto_stringer) = false; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (*Test) ProtoMessage() {}
7 gogoproto.gostring
这个选项为message级别,为true的时候,gogo会为相应的message生成GoString()方法。如果想为所有的message生成之类函数,可以设置package级别的gogoproto.stringer_all为true。
pb code: option (gogoproto.goproto_getters_all) = false; message test { option (gogoproto.gostring) = true; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s }
gogoproto.stringer_all
pb code: option (gogoproto.goproto_getters_all) = false; option (gogoproto.gostring_all) = true; message test { string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s }
测试用例:
package main import ( "fmt" "strings" ) type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s } func main() { var test Test test.MyMsg = "hello, world!" fmt.Printf("%#v\n", test) }
8 populate & populate_all
这个选项为message级别,当设置其值为true的时候,gogo会为每个message生成一个NewPopulated函数。
pb code: option (gogoproto.populate_all) = true; message B { optional A A = 1 [(gogoproto.nullable) = false, (gogoproto.embed) = true]; repeated bytes G = 2 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uint128", (gogoproto.nullable) = false]; } go code: func NewPopulatedB(r randyExample, easy bool) *B { this := &B{} v2 := NewPopulatedA(r, easy) this.A = *v2 if r.Intn(10) != 0 { v3 := r.Intn(10) this.G = make([]github_com_gogo_protobuf_test_custom.Uint128, v3) for i := 0; i < v3; i++ { v4 := github_com_gogo_protobuf_test_custom.NewPopulatedUint128(r) this.G[i] = *v4 } } if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedExample(r, 3) } return this }
如果gogo为message生成了test函数,这些函数就会调用NewPopulated函数。如果用户没有使用这个选项但是使用了test选项,则用户需自定义NewPopulated函数。
由于oschina对博文长度有限制,剩余部分转下文《gogoprotobuf使用(下)》。