这个教程中将会描述protocol buffer编译器经过给定的.proto
会编译生成什么Go代码。教程针对的是proto3版本的protobuf。在阅读以前确保你已经阅读过Protobuf语言指南。git
Protobuf核心的工具集是C++语言开发的,官方的protoc编译器中并不支持Go语言,须要安装一个插件才能生成Go代码。用以下命令安装:github
$ go get github.com/golang/protobuf/protoc-gen-go
复制代码
提供了一个protoc-gen-go
二进制文件,当编译器调用时传递了--go_out
命令行标志时protoc
就会使用它。--go_out
告诉编译器把Go源代码写到哪里。编译器会为每一个.proto
文件生成一个单独的源代码文件。golang
输出文件的名称是经过获取.proto文件的名称并进行两处更改来计算的:bash
.pb.go
。好比说player_record.proto
编译后会获得player_record.pb.go
。--proto_path
或-I
命令行标志指定)将替换为输出路径(使用--go_out
标志指定)。当你运行以下编译命令时:markdown
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
和build/gen/bar/baz.pb.go
函数
若是有必要,编译器会自动生成build/gen/bar
目录,可是他不能建立build
或者build/gen
目录,这两个必须是已经存在的目录。工具
若是一个.proto
文件中有包声明,生成的源代码将会使用它来做为Go的包名,若是.proto
的包名中有.
在Go包名中会将.
转换为_
。举例来讲proto
包名example.high_score
将会生成Go包名example_high_score
。oop
在.proto
文件中可使用option go_package
指令来覆盖上面默认生成Go包名的规则。好比说包含以下指令的一个.proto
文件post
package example.high_score; option go_package = "hs"; 复制代码
生成的Go源代码的包名是hs
。ui
若是一个.proto
文件中不包含package声明,生成的源代码将会使用.proto
文件的文件名(去掉扩展名)做为Go包名,.
会被首先转换为_
。举例来讲一个名为high.score.proto
不包含pack声明的文件将会生成文件high.score.pb.go
,他的Go包名是high_score
。
一个简单的消息声明:
message Foo {}
复制代码
protocol buffer编译器将会生成一个名为Foo
的结构体,实现了proto.Message
接口的Foo
类型的指针
type Foo struct { } // 重置proto为默认值 func (m *Foo) Reset() { *m = Foo{} } // String 返回proto的字符串表示 func (m *Foo) String() string { return proto.CompactTextString(m) } // ProtoMessage做为一个tag 确保其余人不会意外的实现 // proto.Message 接口. func (*Foo) ProtoMessage() {} 复制代码
一个message能够声明在其余message的内部。好比说:
message Foo {
message Bar {
}
}
复制代码
这种状况,编译器会生成两个结构体:Foo
和Foo_Bar
。
Protobufs带有一组预约义的消息,称为众所周知的类型(WKT)。这些类型能够用于与其余服务的互操做性,或者仅仅由于它们简洁地表示了常见的有用模式。例如,Struct消息表示任意C样式结构的格式。
WKT的预生成Go代码做为Go protobuf库的一部分进行分发,若是message中使用了WKT,则生成的消息的Go代码会引用此代码。例如,给出以下消息:
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 } 复制代码
通常来讲,您不须要将这些类型直接导入代码中。可是,若是须要直接引用其中一种类型,只需导入github.com/golang/protobuf/ptypes/[TYPE]包,并正常使用该类型。
编译器会为每一个在message中定义的字段生成一个Go结构体的字段,字段的确切性质取决于它的类型以及它是singular
,repeated
,map
仍是oneof
字段。
注意生成的Go结构体的字段将始终使用驼峰命名,即便在.proto
文件中消息字段用的是小写加下划线(应该这样)。大小写转换的原理以下:
_
,它将被替换为X。所以,proto字段foo_bar_baz
在Go中变成FooBarBaz
, _my_field_name_2
变为XMyFieldName_2
。
对于字段定义:
int32 foo = 1;
复制代码
编译器将生成一个带有名为Foo的int32字段和一个访问器方法GetFoo()的结构,该方法返回Foo中的int32值或该字段的零值(若是字段未设置(数值型零值为0,字符串为空字符串))。
给出以下消息类型
message Bar {}
复制代码
对于一个有Bar
类型字段的消息:
// proto3
message Baz {
Bar foo = 1;
}
复制代码
编译器将会生成一个Go结构体
type Baz struct { Foo *Bar } 复制代码
消息类型的字段能够设置为nil,这意味着该字段未设置,有效清除该字段。这不等同于将值设置为消息结构体的“空”实例。
编译器还生成一个func(m * Baz)GetFoo()* Bar
辅助函数。这让不在中间检查nil值进行链式调用成为可能。
每一个重复的字段在Go中的结构中生成一个T类型的slice,其中T是字段的元素类型。对于带有重复字段的此消息:
message Baz {
repeated Bar foo = 1;
}
复制代码
编译器会生成以下结构体:
type Baz struct { Foo []*Bar } 复制代码
一样,对于字段定义repeated bytes foo = 1;
编译器将会生成一个带有类型为[][]byte
名为Foo
的字段的Go结构体。对于可重复的枚举repeated MyEnum bar = 2;
,编译器会生成带有类型为[]MyEnum
名为Bar
的字段的Go结构体。
每一个映射字段会在Go的结构体中生成一个map[TKey]TValue
类型的字段,其中TKey
是字段的键类型TValue
是字段的值类型。对于下面这个消息定义:
message Bar {}
message Baz {
map<string, Bar> foo = 1;
}
复制代码
编译器生成Go结构体
type Baz struct { Foo map[string]*Bar } 复制代码
给出以下枚举
message SearchRequest {
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 1;
...
}
复制代码
编译器将会生成一个枚举类型和一系列该类型的常量。
对于消息中的枚举(像上面那样),类型名字以消息名开头
type SearchRequest_Corpus int32 复制代码
对于包级别的枚举:
// .proto
enum Foo {
DEFAULT_BAR = 0;
BAR_BELLS = 1;
BAR_B_CUE = 2;
}
复制代码
Go 中的类型不会对proto中的枚举名称进行修改:
type Foo int32 复制代码
此类型具备String()
方法,该方法返回给定值的名称。
Enum()
方法使用给定值初始化新分配的内存并返回相应的指针:
func (Foo) Enum() *Foo
复制代码
编译器为枚举中的每一个值生成一个常量。对于消息中的枚举,常量以消息的名称开头:
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
)
复制代码
对于包级别的枚举,常量以枚举名称开头:
const (
Foo_DEFAULT_BAR Foo = 0
Foo_BAR_BELLS Foo = 1
Foo_BAR_B_CUE Foo = 2
)
复制代码
protobuf编译器还生成从整数值到字符串名称的映射以及从名称到值的映射:
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
语言容许多个枚举符号具备相同的数值。具备相同数值的符号是同义词。这些在Go中以彻底相同的方式表示,多个名称对应于相同的数值。反向映射包含数字值的单个条目,数值映射到出如今proto
文件中首先出现的名称。
默认状况下,Go代码生成器不会为服务生成输出。若是您启用gRPC插件(请参阅gRPC Go快速入门指南),则会生成代码以支持gRPC。