12

Protobuf 生成 Go 代码指南

 4 years ago
source link: https://mp.weixin.qq.com/s/xbyrj56IdrdHFOi07dM46w
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这个教程中将会描述protocol buffer编译器通过给定的 .proto 会编译生成什么Go代码。教程针对的是proto3版本的protobuf。在阅读之前确保你已经阅读过Protobuf语言指南。

编译器调用

Protobuf核心的工具集是C++语言开发的,官方的protoc编译器中并不支持Go语言,需要安装一个插件才能生成Go代码。用如下命令安装:

提供了一个 protoc-gen-go 二进制文件,当编译器调用时传递了 --go_out 命令行标志时 protoc 就会使用它。 --go_out 告诉编译器把Go源代码写到哪里。编译器会为每个 .proto 文件生成一个单独的源代码文件。

输出文件的名称是通过获取.proto文件的名称并进行两处更改来计算的:

  • 生成文件的扩展名是  .pb.go 。比如说  player_record.proto 编译后会得到  player_record.pb.go

  • proto路径(使用  --proto_path 或  -I 命令行标志指定)将替换为输出路径(使用  --go_out 标志指定)。

当你运行如下编译命令时:

编译器会读取文件 src/foo.protosrc/bar/baz.proto ,这将会生成两个输出文件 build/gen/foo.pb.gobuild/gen/bar/baz.pb.go

如果有必要,编译器会自动生成 build/gen/bar 目录,但是他不能创建 build 或者 build/gen 目录,这两个必须是已经存在的目录。

如果一个 .proto 文件中有包声明,生成的源代码将会使用它来作为Go的包名,如果 .proto 的包名中有 . 在Go包名中会将 . 转换为 _ 。举例来说 proto 包名 example.high_score 将会生成Go包名 example_high_score

.proto 文件中可以使用option go_package 指令来覆盖上面默认生成Go包名的规则。比如说包含如下指令的一个 .proto 文件

生成的Go源代码的包名是 hs

如果一个 .proto 文件中不包含package声明,生成的源代码将会使用 .proto 文件的文件名(去掉扩展名)作为Go包名, . 会被首先转换为 _ 。举例来说一个名为 high.score.proto 不包含pack声明的文件将会生成文件 high.score.pb.go ,他的Go包名是 high_score

消息

一个简单的消息声明:

protocol buffer编译器将会生成一个名为 Foo 的结构体,实现了 proto.Message 接口的 Foo 类型的指针

内嵌的消息

一个message可以声明在其他message的内部。比如说:

这种情况,编译器会生成两个结构体: FooFoo_Bar

预定义消息类型

Protobufs带有一组预定义的消息,称为众所周知的类型(WKT)。这些类型可以用于与其他服务的互操作性,或者仅仅因为它们简洁地表示了常见的有用模式。例如,Struct消息表示任意C样式结构的格式。

WKT的预生成Go代码作为Go protobuf库的一部分进行分发,如果message中使用了WKT,则生成的消息的Go代码会引用此代码。例如,给出如下消息:

生成的Go代码将会像下面这样:

一般来说,您不需要将这些类型直接导入代码中。但是,如果需要直接引用其中一种类型,只需导入github.com/golang/protobuf/ptypes/[TYPE]包,并正常使用该类型。

字段

编译器会为每个在message中定义的字段生成一个Go结构体的字段,字段的确切性质取决于它的类型以及它是 singularrepeatedmap 还是 oneof 字段。

注意生成的Go结构体的字段将始终使用驼峰命名,即使在 .proto 文件中消息字段用的是小写加下划线(应该这样)。大小写转换的原理如下:

  • 首字母会大些,如果message中字段的第一个字符是  _ ,它将被替换为X。

  • 如果内部下划线后跟小写字母,则删除下划线,并将后面跟随的字母大写。

因此,proto字段 foo_bar_baz 在Go中变成 FooBarBaz_my_field_name_2 变为 XMyFieldName_2

单一标量字段

对于字段定义:

编译器将生成一个带有名为Foo的int32字段和一个访问器方法GetFoo()的结构,该方法返回Foo中的int32值或该字段的零值(如果字段未设置(数值型零值为0,字符串为空字符串))。

单一message字段

给出如下消息类型

对于一个有 Bar 类型字段的消息:

编译器将会生成一个Go结构体

消息类型的字段可以设置为nil,这意味着该字段未设置,有效清除该字段。这不等同于将值设置为消息结构体的“空”实例。

编译器还生成一个 funcm*BazGetFoo()*Bar 辅助函数。这让不在中间检查nil值进行链式调用成为可能。

可重复字段

每个重复的字段在Go中的结构中生成一个T类型的slice,其中T是字段的元素类型。对于带有重复字段的此消息:

编译器会生成如下结构体:

同样,对于字段定义 repeated bytes foo=1; 编译器将会生成一个带有类型为 [][]byte 名为 Foo 的字段的Go结构体。对于可重复的枚举 repeatedMyEnumbar=2; ,编译器会生成带有类型为 []MyEnum 名为 Bar 的字段的Go结构体。

映射字段

每个映射字段会在Go的结构体中生成一个 map[TKey]TValue 类型的字段,其中 TKey 是字段的键类型 TValue 是字段的值类型。对于下面这个消息定义:

编译器生成Go结构体

枚举

给出如下枚举

编译器将会生成一个枚举类型和一系列该类型的常量。

对于消息中的枚举(像上面那样),类型名字以消息名开头

对于包级别的枚举:

Go 中的类型不会对proto中的枚举名称进行修改:

此类型具有 String() 方法,该方法返回给定值的名称。

Enum() 方法使用给定值初始化新分配的内存并返回相应的指针:

编译器为枚举中的每个值生成一个常量。对于消息中的枚举,常量以消息的名称开头:

对于包级别的枚举,常量以枚举名称开头:

protobuf编译器还生成从整数值到字符串名称的映射以及从名称到值的映射:

请注意, .proto 语言允许多个枚举符号具有相同的数值。具有相同数值的符号是同义词。这些在Go中以完全相同的方式表示,多个名称对应于相同的数值。反向映射包含数字值的单个条目,数值映射到出现在 proto 文件中首先出现的名称。

服务

默认情况下,Go代码生成器不会为服务生成输出。如果您启用gRPC插件(请参阅gRPC Go快速入门指南),则会生成代码以支持gRPC。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK