前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >protocol buffers生成go代码原理

protocol buffers生成go代码原理

作者头像
charlieroro
发布2020-03-24 12:09:52
1.7K0
发布2020-03-24 12:09:52
举报
文章被收录于专栏:charlierorocharlieroro

本文描述了protocol buffers使用.proto文件生成pb.go文件的过程

编译器

  编译器需要插件来编译环境,使用如下方式安装插件:go get github.com/golang/protobuf/protoc-gen-go

  使用.proto生成的文件相比输入文件有如下两处变更:

    • 生成文件的文件名为:输入文件的扩展名.pb.go,如使用player.proto生成的文件名为player.pb.go
    • 生成文件的路径为--go_out指定的文件

  当执行如下命令时:

代码语言:javascript
复制
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 and build/gen/bar/baz.pb.go。编译器会自动生成build/gen/bar目录,但不会生成build或build/gen目录。

 如果.proto文件包含包定义,则生成的代码会使用.proto的package,与go的package处理类似,会将package名字中的"."转换为"_"。如proto package名为example.high_score,对应生成的代码的package name为example_high_score。

  使用go_package选项可以替换默认条件下.proto生成的package name。如下生成的go package为“hs".

代码语言:javascript
复制
package example.high_score;
option go_package = "hs";

  如果.proto文件中没有包含package声明,则生成的代码会使用文件名(处理方式类似go package name)

消息

下面是一个简单的message

代码语言:javascript
复制
message Foo {}

  protocol buffer 编译器会生成一个struct,名为Foo。A *Foo实现了该接口的方法。下述成员会出现在所有message生成的go代码中

代码语言:javascript
复制
type Foo struct {
}

// Reset sets the proto's state to default values.
func (m *Foo) Reset()         { *m = Foo{} }

// String returns a string representation of the proto.
func (m *Foo) String() string { return proto.CompactTextString(m) }

// ProtoMessage acts as a tag to make sure no one accidentally implements the
// proto.Message interface.
func (*Foo) ProtoMessage()    {}

内嵌类型

如下内嵌场景下会生成2个独立的struct,Foo和Foo_Bar

代码语言:javascript
复制
message Foo {
  message Bar {
  }
}

Well_known 类型

  protocol buffer的预定义消息集合,称为well_known types(WKTs)。这些类型在与其他服务交互时比较好用。如Struct消息表示了任意的C风格的struct。

  为WTKs预生成的go代码作为Go protobuf library的一部分发布。如给出一个message  

代码语言:javascript
复制
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代码如下:  

代码语言:javascript
复制
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
}

字段 

  生成的go字段名称遵循驼峰命名法,规则如下:

    • 首字母大写,如果首字符是下划线,则使用大写X替换该下划线
    • 如果字符内部的下划线后跟着小写的字母,则移除该下划线,并将原来下划线后面的字母大写

  如foo_bar_baz变为FooBarBaz,_my_field_name_2变为XMyFieldName_2

Singular Scalar Fields (proto3)

代码语言:javascript
复制
int32 foo = 1;

  编译器会生成一个包含名为int32字段,名为Foo的struct,以及一个名为GetFoo()的方法,该方法会返回Foo中定义的int32的值,或默认值(如果设置初始值)

Singular Message Fields

代码语言:javascript
复制
message Bar {}
代码语言:javascript
复制
message Baz {
  Bar foo = 1;
}

  针对message Baz,编译器会生成如下struct,以及一个func (m *Baz)GetFoo() *Bar的函数。

代码语言:javascript
复制
type Baz struct {
    Foo *Bar //结构体使用指针
}

Repeated Fields

代码语言:javascript
复制
message Baz {
  repeated Bar foo = 1;
}

  生成如下struct。类似地,如果字段定义为 repeated bytes foo = 1,编译器会生成名为Foo,含[][]byte字段的Go struct;字段定义为 repeated MyEnum bar = 2,则会生成名为Bar,包含[]MyEnum字段的struct

代码语言:javascript
复制
type Baz struct {
    Foo  []*Bar //相比不带repead的,多了"[]"
}

Map Fields

代码语言:javascript
复制
message Bar {}

message Baz {
  map<string, Bar> foo = 1;
}

  编译器生成如下struct

代码语言:javascript
复制
type Baz struct{
    Foo map[string]*Bar  //map中的结构体也是指针表达方式
}

Oneof Fields

  针对oneof字段,protobuf编译器会生成接口类型 isMessageName_MyField。此外oneof中的每个singular字段会生成struct,isMessageName_MyField接口。如下oneof:  

代码语言:javascript
复制
package account;
message Profile {
  oneof avatar {
    string image_url = 1;
    bytes image_data = 2;
  }
}

  编译器会生成struct:  

代码语言:javascript
复制
type Profile struct {
        // Types that are valid to be assigned to Avatar:
        //      *Profile_ImageUrl
        //      *Profile_ImageData
        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 编译器同时会生成func (m *Profile) GetImageUrl() string 和func (m *Profile) GetImageData() []byte

  如下展示了如何设置字段:  

代码语言:javascript
复制
p1 := &account.Profile{
  Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"},
}

// imageData is []byte
imageData := getImageData()
p2 := &account.Profile{
  Avatar: &account.Profile_ImageData{imageData},
}

  可以使用如下来处理不同的message类型

代码语言:javascript
复制
switch x := m.Avatar.(type) {
case *account.Profile_ImageUrl:
        // Load profile image based on URL
        // using x.ImageUrl
case *account.Profile_ImageData:
        // Load profile image based on bytes
        // using x.ImageData
case nil:
        // The field is not set.
default:
        return fmt.Errorf("Profile.Avatar has unexpected type %T", x)
}

Enumerations

代码语言:javascript
复制
message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 1;
  ...
}

  protocol buffer会生成一个类型以及一系列该类型表示的常量。在message内部的enums,type的名称会以message名称开头:

代码语言:javascript
复制
type SearchRequest_Corpus int32
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
)

  package级别的enum

代码语言:javascript
复制
enum Foo {
  DEFAULT_BAR = 0;
  BAR_BELLS = 1;
  BAR_B_CUE = 2;
}

Go类型以原来的enum,该类型还有一个String()方法来返回给定值的名字,Enum()方法初始化并分配给定值的内存,返回相应的指针。

代码语言:javascript
复制
type Foo int32
func (Foo) Enum() *Foo

  protocol buffer编译器也会整数到字符串名称以及名称到数值的对应关系  

代码语言:javascript
复制
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允许多enum的数值相同。由于多名称对应一个数值,逆向对应关系则是数值与.proto文件中出现的第一个名称相对应(一个对应关系)。

service

  Go代码生成器默认不会为services生成代码。如果使能了gRPC插件,则可以支持个RPC代码的生成。

参见:GO Generated Code

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-05-15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档