protocol buffers生成go代碼原理


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

編譯器

  編譯器需要插件來編譯環境,使用如下方式安裝插件:go get github.com/golang/protobuf/protoc-gen-go

  使用.proto生成的文件相比輸入文件有如下兩處變更:

    • 生成文件的文件名為:輸入文件的擴展名.pb.go,如使用player.proto生成的文件名為player.pb.go
    • 生成文件的路徑為--go_out指定的文件

  當執行如下命令時:

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".

package example.high_score;
option go_package = "hs";

  如果.proto文件中沒有包含package聲明,則生成的代碼會使用文件名(處理方式類似go package name)

消息

下面是一個簡單的message

message Foo {}

  protocol buffer 編譯器會生成一個struct,名為Foo。A *Foo實現了該接口的方法。下述成員會出現在所有message生成的go代碼中

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

message Foo {
  message Bar {
  }
}

Well_known 類型

  protocol buffer的預定義消息集合,稱為well_known types(WKTs)。這些類型在與其他服務交互時比較好用。如Struct消息表示了任意的C風格的struct。

  為WTKs預生成的go代碼作為Go protobuf library的一部分發布。如給出一個message  

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
}

字段 

  生成的go字段名稱遵循駝峰命名法,規則如下:

    • 首字母大寫,如果首字符是下划線,則使用大寫X替換該下划線
    • 如果字符內部的下划線后跟着小寫的字母,則移除該下划線,並將原來下划線后面的字母大寫

  如foo_bar_baz變為FooBarBaz,_my_field_name_2變為XMyFieldName_2

  Singular Scalar Fields (proto3)

int32 foo = 1;

  編譯器會生成一個包含名為int32字段,名為Foo的struct,以及一個名為GetFoo()的方法,該方法會返回Foo中定義的int32的值,或默認值(如果設置初始值)

    Singular Message Fields

message Bar {}
message Baz {
  Bar foo = 1;
}

  針對message Baz,編譯器會生成如下struct,以及一個func (m *Baz)GetFoo() *Bar的函數。

type Baz struct {
    Foo *Bar //結構體使用指針
}

  Repeated Fields   

message Baz {
  repeated Bar foo = 1;
}

  生成如下struct。類似地,如果字段定義為 repeated bytes foo = 1,編譯器會生成名為Foo,含[][]byte字段的Go struct;字段定義為 repeated MyEnum bar = 2,則會生成名為Bar,包含[]MyEnum字段的struct

type Baz struct {
    Foo  []*Bar //相比不帶repead的,多了"[]"
}

  Map Fields  

message Bar {}

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

  編譯器生成如下struct

type Baz struct{
    Foo map[string]*Bar  //map中的結構體也是指針表達方式
}

  Oneof Fields

  針對oneof字段,protobuf編譯器會生成接口類型 isMessageName_MyField。此外oneof中的每個singular字段會生成struct,isMessageName_MyField接口。如下oneof:  

package account;
message Profile {
  oneof avatar {
    string image_url = 1;
    bytes image_data = 2;
  }
}

  編譯器會生成struct:  

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

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

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類型

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  

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名稱開頭:

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

enum Foo {
  DEFAULT_BAR = 0;
  BAR_BELLS = 1;
  BAR_B_CUE = 2;
}

Go類型以原來的enum,該類型還有一個String()方法來返回給定值的名字,Enum()方法初始化並分配給定值的內存,返回相應的指針。

type Foo int32
func (Foo) Enum() *Foo

  protocol buffer編譯器也會整數到字符串名稱以及名稱到數值的對應關系  

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM