反射

Reflection(反射)是新实现的旗舰特性。与 reflect 包提供 Go 类型和值的视图相似,protoreflect 包根据 protocol buffer 类型系统提供值的视图。

完整的描述 protoreflect package 对于这篇文章来说太长了,但是,我们可以来看看如何编写前面提到的日志清理函数。

首先,我们将编写 .proto 文件来定义 google.protobuf.FieldOptions 类型的扩展名,以便我们可以将注释字段作为标识敏感信息的与否。

1
2
3
4
5
6
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
    bool non_sensitive = 50000;
}

我们可以使用此选项来将某些字段标识为非敏感字段。

1
2
3
message MyMessage {
    string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}

接下来,我们将编写一个 Go 函数,它用于接收任意 message 值以及删除所有敏感字段。

1
2
3
4
// 清除 pb 中所有的敏感字段
func Redact(pb proto.Message) {
   // ...
}

函数接收 proto.Message 参数,这是由所有已生成的 message 类型实现的接口类型。此类型是 protoreflect 包中已定义的别名:

1
2
3
type ProtoMessage interface{
    ProtoReflect() Message
}

为了避免填充生成 message 的命名空间,接口仅包含一个返回 protoreflect.Message 的方法,此方法提供对 message 内容的访问。

(为什么是别名?由于 protoreflect.Message 有返回原始 proto.Message 的相应方法,我们需要避免在两个包中循环导入。)

protoreflect.Message.Range 方法为 message 中的每一个填充字段调用一个函数。

1
2
3
4
5
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
    // ...
    return true
})

使用描述 protocol buffer 类型的 protoreflect.FieldDescriptor 字段和包含字段值的 protoreflect.Value 字段来调用 range 函数。

protoreflect.FieldDescriptor.Options 方法以 google.protobuf.FieldOptions message 的形式返回字段选项。

1
opts := fd.Options().(*descriptorpb.FieldOptions)

(为什么使用类型断言?由于生成的 descriptorpb package 依赖于 protoreflect,所以 protoreflect package 无法返回正确的选项类型,否则会导致循环导入的问题)

然后,我们可以检查选项以查看扩展为 boolean 类型的值:

1
2
3
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
    return true // 不要删减非敏感字段
}

请注意,我们在这里看到的是字段描述符,而不是字段值,我们感兴趣的信息在于 protocol buffer 类型系统,而不是 Go 语言。

这也是我们已经简化了 proto package API 的一个示例,原来的 proto.GetExtension 返回一个值和错误信息,新的 proto.GetExtension 只返回一个值,如果字段不存在,则返回该字段的默认值。在 Unmarshal 的时候报告扩展解码错误。

一旦我们确定了需要修改的字段,将其清除就很简单了:

1
m.Clear(fd)

综上所述,我们完整的修改函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 清除 pb 中的所有敏感字段
func Redact(pb proto.Message) {
    m := pb.ProtoReflect()
    m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
        opts := fd.Options().(*descriptorpb.FieldOptions)
        if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
            return true
        }
        m.Clear(fd)
        return true
    })
}

一个更加完整的实现应该是以递归的方式深入这些 message 值字段。我们希望这些简单的示例能让你更了解 protocol buffer reflection(反射)以及它的用法。

动态 protobuf 的原理

这里的动态并不是指字段可以随便改变,而是在运行时根据代码逻辑构建出 FieldDescriptorProto,并以此为字段的定义依据通过反射来序列化 / 反序列化 protobuf 消息。

静态 pb 动态 pb
字段定义 通过.proto 文件定义 在运行时通过代码逻辑定义,产物是 FileDescriptor
编译 通过 protoc 进行编译,生成固定 struct 和 Marshal,Unmarshal 等操作方法的 go 代码 无需编译
使用 调用生成的代码对消息进行序列化和反序列化等操作 使用 protoreflect 下面的反射方法,依据 FileDescriptor 的定义,对消息进行序列化和反序列化等操作

某个服务,启动时构建 FileDescriptor,从里面掏一个 MessageDescriptor 出来,用它创建一个 Message 对象,并将数据塞进去,最后 Marshal 成二进制(也称为 wire format)。

另一个服务,启动时使用同样的过程构建 FileDescriptor,从里面把 MessageDescriptor 掏出来,用它创建一个 Message 对象,把之前的那个二进制 Unmarshal 进去。然后按照 MessageDescriptor 里面的各种 FieldDescriptor(也就是字段定义)用 Message 对象上的一些反射方法把字段的数据取出来。

准备

go get 库

1
go get google.golang.org/protobuf@v1.21.0

然后记得 import,下文基本上用了这四个包:

1
2
3
4
5
6
import (
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protodesc"
	pref "google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/descriptorpb"
)

如何定义

下面的代码定义了三个 message,其中 Foo 是简单的无嵌套消息;Bar 是消息内嵌套一个 Map 字段,key 是 string,value 是前面定义的那个 Foo;Baz 是消息内嵌套 repeated 也就是列表字段,其中元素的类型是 Foo。

先定义 FileDescriptorProto,在里面塞 MessageDescriptorProto,最后记得用 protodesc.NewFile(pb, nil) 来通过那个 FileDescriptor 生成可用的 FileDescriptor,这个东西才是最终我们需要的。

简单消息定义

嵌套 Map 消息定义

其实是使用 repeated 里面塞内嵌 k/v 的 message 来表示 map 的,但在通过代码动态创建 descriptor 的时候,就要完全符合它的要求才可以,不然在后续的使用中会报错。

什么样的字段会被认为是个 map 呢?主要需要注意以下几点:

  • Label 为 Repeated
  • Type 为 MessageKind
  • TypeName 字段需要设置为.包名.消息名.entryDescriptor名,例如.example.Bar.BarMapEntry,这个需要和 NestedType 里面相一致
  • NestedType 字段里需要创建一个 entry 的 DescriptorProto
    • 这个东西的 Name 必须是你 map 字段名改成驼峰后面解 Entry,也就是说如果你的 map 字段是 what_the_fuck,那么这里的 Name 必须设置为 WhatTheFuckEntry,
    • 他的 NestedType 的 Field 里有且仅有两个,分别是 key 和 value,而且顺序一定要是先 key 后 value,之后 key 和 value 的的 Type 什么的根据你的需要进行设置
    • 它的 Options 字段里面要设置 MapEntry: proto.Bool(true)

嵌套 List 消息定义

嵌套 List 定义很简单,Label 为 Repeated,Type 根据需要进行设置。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// make FileDescriptorProto
pb := &descriptorpb.FileDescriptorProto{
    Syntax:      proto.String("proto3"),
    Name:        proto.String("example.proto"),
    Package:     proto.String("example"),
    MessageType: []*descriptorpb.DescriptorProto{
        // define Foo message
        &descriptorpb.DescriptorProto{
            Name: proto.String("Foo"),
            Field: []*descriptorpb.FieldDescriptorProto{
                {
                    Name:     proto.String("id"),
                    JsonName: proto.String("id"),
                    Number:   proto.Int32(1),
                    Type:     descriptorpb.FieldDescriptorProto_Type(pref.Int32Kind).Enum(),
                },
                {
                    Name:     proto.String("title"),
                    JsonName: proto.String("title"),
                    Number:   proto.Int32(2),
                    Type:     descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
                },
            },
        },

        // define Bar message
        &descriptorpb.DescriptorProto{
            Name: proto.String("Bar"),
            Field: []*descriptorpb.FieldDescriptorProto{
                {
                    Name:     proto.String("bar_map"),
                    JsonName: proto.String("bar_map"),
                    Number:   proto.Int32(1),
                    Label:    descriptorpb.FieldDescriptorProto_Label(pref.Repeated).Enum(),
                    Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
                    TypeName: proto.String(".example.Bar.BarMapEntry"),
                },
            },
            NestedType: []*descriptorpb.DescriptorProto{
                {
                    Name: proto.String("BarMapEntry"),
                    Field: []*descriptorpb.FieldDescriptorProto{
                        {
                            Name:     proto.String("key"),
                            JsonName: proto.String("key"),
                            Number:   proto.Int32(1),
                            Label:    descriptorpb.FieldDescriptorProto_Label(pref.Optional).Enum(),
                            Type:     descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
                        }, {
                            Name:     proto.String("value"),
                            JsonName: proto.String("value"),
                            Number:   proto.Int32(2),
                            Label:    descriptorpb.FieldDescriptorProto_Label(pref.Optional).Enum(),
                            Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
                            TypeName: proto.String(".example.Foo"),
                        },
                    },
                    Options: &descriptorpb.MessageOptions{
                        MapEntry: proto.Bool(true),
                    },
                },
            },
        },

        // define Baz message
        &descriptorpb.DescriptorProto{
            Name: proto.String("Baz"),
            Field: []*descriptorpb.FieldDescriptorProto{
                {
                    Name:     proto.String("baz_list"),
                    JsonName: proto.String("baz_list"),
                    Number:   proto.Int32(1),
                    Label:    descriptorpb.FieldDescriptorProto_Label(pref.Repeated).Enum(),
                    Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
                    TypeName: proto.String(".example.Foo"),
                },
            },
        },

    },
}

// get FileDescriptor
fd, err := protodesc.NewFile(pb, nil)

上面的定义基本等效于这个 proto 文件,实际的逻辑中不会用到这个文件,仅用作参考,理论上来说用它编译出的 go 代码也可以正常用来操作动态定义出来的 pb 生成的数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
syntax = "proto3";
package myproto;

message Foo {
    int id = 1;
    string title = 2;
    string content = 3;
}

message Bar {
    Map<string,Foo> bar_map = 1;
}

message Bazz {
    repeated int my_list = 1;
}

如何创建消息及序列化

简单消息

往 message 里塞数据的套路基本都是一样的,先获取要修改的字段的 FieldDescriptor,这个东西可以通过 abcMessageDescriptor.Fields().ByName("field_name") 获取,或者也有 ByNumber 方法等传入字段序号获取,用法类似。取到 FieldDescriptor 之后就可以用 Set 方法把值设置上去了。

1
2
3
4
5
6
7
8
9
var (
    msg  *dynamicpb.Message
    data []byte
)

fooMessageDescriptor := fd.Messages().ByName("Foo")
msg := dynamicpb.NewMessage(fooMessageDescriptor)
msg.Set(fooMessageDescriptor.Fields().ByName("id"), pref.ValueOfInt32(42))
msg.Set(fooMessageDescriptor.Fields().ByNumber(1), pref.ValueOfString("aloha"))

嵌套 Map

先取到 Map 字段的 FieldDescriptor,然后传入到 NewField 方法,获取 Map 字段,使用 Set 往里面塞数据,最后别忘了把 Map 通过 Set 方法写入到 msg 中,如果你要写入的 Value 是可变的,要用 Mutable 方法进行操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
barMessageDescriptor := fd.Messages().ByName("Bar")
msg := dynamicpb.NewMessage(barMessageDescriptor)
mf := barMessageDescriptor.Fields().ByName("bar_map")
mp:= msg.NewField(mf)

fooMsg := makeFooMsg(fd)

mp.Map().Set(pref.MapKey(pref.ValueOfString("key1")), pref.ValueOfMessage(fooMsg))
mp.Map().Set(pref.MapKey(pref.ValueOfString("key2")), pref.ValueOfMessage(fooMsg))
msg.Set(mf, mp)

嵌套 List (即 repeated)

先取到 List 字段的 FieldDescriptor,然后传入到 NewField 方法,获取 List,使用 Append 往里面塞元素,最后别忘了把 List 通过 Set 方法写入到 msg 中,如果你要写入的 Value 是可变的,要用 MutableAppend 方法操作。

1
2
3
4
5
6
7
8
9
bazMessageDescriptor := fd.Messages().ByName("Baz")
msg := dynamicpb.NewMessage(bazMessageDescriptor)
lf := bazMessageDescriptor.Fields().ByName("baz_list")
fooMsg := makeFooMsg(fd)
lst := msg.NewField(lf).List()
lst.Append(pref.ValueOf(fooMsg))
lst.Append(pref.ValueOf(fooMsg))
lst.Append(pref.ValueOf(fooMsg))
msg.Set(lf, pref.ValueOf(lst))

如何反序列化消息并获取字段数据

反序列化

先用 dynamicpb.NewMessage 传入需要的 MessageDescriptor 新建 Message 对象,再调用 proto.Unmarshal 方法,把数据解到 msg 里面。

1
2
3
4
5
6
7
8
9
var (
    data []byte
    err error
    )
barMessageDescriptor := fd.Messages().ByName("Bar")
msg := dynamicpb.NewMessage(barMessageDescriptor)
if err := proto.Unmarshal(data, msg); err != nil {
    panic(err)
}

获取普通字段数据

用 Message 上的 Get 方法传需要的字段的 descriptor 进去,就可以取到值,用 Range 方法可以传入一个函数对各个字段进行遍历,return false 的时候可以直接跳出循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fooMessageDescriptor := fd.Messages().ByName("Foo")
msg := dynamicpb.NewMessage(fooMessageDescriptor)
if err := proto.Unmarshal(data, msg); err != nil {
    panic(err)
}

// get single field's value
v := msg.Get(fooMessageDescriptor.Fields().ByName("id"))
fmt.Printf("get %v \n", v)

// iterate over all fields
msg.Range(func(descriptor pref.FieldDescriptor, value pref.Value) bool {
    fmt.Printf("field: %v value: %v \n", descriptor.Name(), value)
    return true
})

获取嵌套 Map 数据

用 Message 上的 Get 方法传 Map 字段的 descriptor 进去,return false 的时候可以直接跳出循环。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
barMessageDescriptor := fd.Messages().ByName("Bar")
msg := dynamicpb.NewMessage(barMessageDescriptor)
if err := proto.Unmarshal(data, msg); err != nil {
    panic(err)
}
mp := msg.Get(barMessageDescriptor.Fields().ByName("bar_map")).Map()

// iterate over map field
mp.Range(func(key pref.MapKey, value pref.Value) bool {
    fmt.Printf("key: %v value: %v  \n", key.String(), value.Message())
    return true
})

获取 List (即 repeated) 数据

用 Message 上的 Get 方法传 List 字段的 descriptor 进去,然后先用 Len 获取长度,再用 Get 方法传入 index 获取各个元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bazMessageDescriptor := fd.Messages().ByName("Baz")
msg := dynamicpb.NewMessage(bazMessageDescriptor)
if err := proto.Unmarshal(data, msg); err != nil {
    panic(err)
}
lf := bazMessageDescriptor.Fields().ByName("baz_list")
lst := msg.Get(lf).List()
length := lst.Len()
for i:= 0; i<length; i++ {
    ele := lst.Get(i)
    fmt.Printf("index: %v value: %v  \n", i, ele.Message())
}

总结

理清这几个东西的关系,就能弄清这个动态 pb 是怎么玩的了。简单概述如下:

  • 创建 FileDescriptorProto 对象
    • 往里面放 descriptorpb.DescriptorProto 来定义消息结构
      • 里面放 descriptorpb.FieldDescriptorProto 来定义字段
  • 用上面的那个 FileDescriptorProto 创建 FileDescriptor
  • FileDescriptor 里面可以掏出 MessageDescriptor,用它可以新建 dynamicpb.Message
  • dynamicpb.Message 就是相当于原来编译出来 go 代码的那个 message 的结构体,里面存各种数据的
    • 可以通过 proto.Marshal 和 proto.Unmarshal 来序列化和反序列化
    • 要设值或取值时,要从 MessageDescriptor 里获取 FieldDescriptor,再通过这个 FieldDescriptor 来对 Message 设值或取值

源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package main

import (
	"fmt"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protodesc"
	pref "google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/descriptorpb"
	"google.golang.org/protobuf/types/dynamicpb"
)

func makeFileDescriptor() pref.FileDescriptor {
	// make FileDescriptorProto
	pb := &descriptorpb.FileDescriptorProto{
		Syntax:  proto.String("proto3"),
		Name:    proto.String("example.proto"),
		Package: proto.String("example"),
		MessageType: []*descriptorpb.DescriptorProto{
			// define Foo message
			&descriptorpb.DescriptorProto{
				Name: proto.String("Foo"),
				Field: []*descriptorpb.FieldDescriptorProto{
					{
						Name:     proto.String("id"),
						JsonName: proto.String("id"),
						Number:   proto.Int32(1),
						Type:     descriptorpb.FieldDescriptorProto_Type(pref.Int32Kind).Enum(),
					},
					{
						Name:     proto.String("title"),
						JsonName: proto.String("title"),
						Number:   proto.Int32(2),
						Type:     descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
					},
				},
			},

			// define Bar message
			&descriptorpb.DescriptorProto{
				Name: proto.String("Bar"),
				Field: []*descriptorpb.FieldDescriptorProto{
					{
						Name:     proto.String("bar_map"),
						JsonName: proto.String("bar_map"),
						Number:   proto.Int32(1),
						Label:    descriptorpb.FieldDescriptorProto_Label(pref.Repeated).Enum(),
						Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
						TypeName: proto.String(".example.Bar.BarMapEntry"),
					},
				},
				NestedType: []*descriptorpb.DescriptorProto{
					{
						Name: proto.String("BarMapEntry"),
						Field: []*descriptorpb.FieldDescriptorProto{
							{
								Name:     proto.String("key"),
								JsonName: proto.String("key"),
								Number:   proto.Int32(1),
								Label:    descriptorpb.FieldDescriptorProto_Label(pref.Optional).Enum(),
								Type:     descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
							}, {
								Name:     proto.String("value"),
								JsonName: proto.String("value"),
								Number:   proto.Int32(2),
								Label:    descriptorpb.FieldDescriptorProto_Label(pref.Optional).Enum(),
								Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
								TypeName: proto.String(".example.Foo"),
							},
						},
						Options: &descriptorpb.MessageOptions{
							MapEntry: proto.Bool(true),
						},
					},
				},
			},

			// define Baz message
			&descriptorpb.DescriptorProto{
				Name: proto.String("Baz"),
				Field: []*descriptorpb.FieldDescriptorProto{
					{
						Name:     proto.String("baz_list"),
						JsonName: proto.String("baz_list"),
						Number:   proto.Int32(1),
						Label:    descriptorpb.FieldDescriptorProto_Label(pref.Repeated).Enum(),
						Type:     descriptorpb.FieldDescriptorProto_Type(pref.MessageKind).Enum(),
						TypeName: proto.String(".example.Foo"),
					},
				},
			},
		},
	}

	// get FileDescriptor
	fd, err := protodesc.NewFile(pb, nil)
	if err != nil {
		panic(err)
	}
	return fd
}

func makeFooMsg(fd pref.FileDescriptor) *dynamicpb.Message {
	fooMessageDescriptor := fd.Messages().ByName("Foo")
	msg := dynamicpb.NewMessage(fooMessageDescriptor)
	msg.Set(fooMessageDescriptor.Fields().ByName("id"), pref.ValueOfInt32(42))
	msg.Set(fooMessageDescriptor.Fields().ByNumber(2), pref.ValueOfString("aloha"))
	return msg
}

func makeBarMsg(fd pref.FileDescriptor) *dynamicpb.Message {
	barMessageDescriptor := fd.Messages().ByName("Bar")
	msg := dynamicpb.NewMessage(barMessageDescriptor)
	mf := barMessageDescriptor.Fields().ByName("bar_map")
	mp := msg.NewField(mf)

	fooMsg := makeFooMsg(fd)

	mp.Map().Set(pref.MapKey(pref.ValueOfString("key1")), pref.ValueOfMessage(fooMsg))
	mp.Map().Set(pref.MapKey(pref.ValueOfString("key2")), pref.ValueOfMessage(fooMsg))
	msg.Set(mf, mp)
	return msg
}

func makeBazMsg(fd pref.FileDescriptor) *dynamicpb.Message {
	bazMessageDescriptor := fd.Messages().ByName("Baz")
	msg := dynamicpb.NewMessage(bazMessageDescriptor)
	lf := bazMessageDescriptor.Fields().ByName("baz_list")
	fooMsg := makeFooMsg(fd)
	lst := msg.NewField(lf).List()
	lst.Append(pref.ValueOf(fooMsg))
	lst.Append(pref.ValueOf(fooMsg))
	lst.Append(pref.ValueOf(fooMsg))
	msg.Set(lf, pref.ValueOf(lst))
	return msg
}

func useFooMsg(fd pref.FileDescriptor, data []byte) {
	fooMessageDescriptor := fd.Messages().ByName("Foo")
	msg := dynamicpb.NewMessage(fooMessageDescriptor)
	if err := proto.Unmarshal(data, msg); err != nil {
		panic(err)
	}

	// iterate over all fields
	msg.Range(func(descriptor pref.FieldDescriptor, value pref.Value) bool {
		fmt.Printf("field: %v value: %v \n", descriptor.Name(), value)
		return true
	})

	// get single field's value
	v := msg.Get(fooMessageDescriptor.Fields().ByName("id"))
	fmt.Printf("get %v \n", v)
}

func useBarMsg(fd pref.FileDescriptor, data []byte) {
	barMessageDescriptor := fd.Messages().ByName("Bar")
	msg := dynamicpb.NewMessage(barMessageDescriptor)
	if err := proto.Unmarshal(data, msg); err != nil {
		panic(err)
	}
	mp := msg.Get(barMessageDescriptor.Fields().ByName("bar_map")).Map()

	// iterate over map field
	mp.Range(func(key pref.MapKey, value pref.Value) bool {
		fmt.Printf("key: %v value: %v  \n", key.String(), value.Message())
		return true
	})
}

func useBazMsg(fd pref.FileDescriptor, data []byte) {
	bazMessageDescriptor := fd.Messages().ByName("Baz")
	msg := dynamicpb.NewMessage(bazMessageDescriptor)
	if err := proto.Unmarshal(data, msg); err != nil {
		panic(err)
	}
	lf := bazMessageDescriptor.Fields().ByName("baz_list")
	lst := msg.Get(lf).List()
	length := lst.Len()
	for i := 0; i < length; i++ {
		ele := lst.Get(i)
		fmt.Printf("index: %v value: %v  \n", i, ele.Message())
	}
}

func marshalMsg(msg *dynamicpb.Message) []byte {
	var (
		data []byte
		err  error
	)
	if data, err = proto.Marshal(msg); err != nil {
		panic(err)
	}
	return data
}

func main() {
	fd := makeFileDescriptor()
	var (
		msg  *dynamicpb.Message
		data []byte
	)

	// foo
	fmt.Println("example of Foo ---")
	msg = makeFooMsg(fd)
	data = marshalMsg(msg)
	useFooMsg(fd, data)

	// bar
	fmt.Println("example of Bar ---")
	msg = makeBarMsg(fd)
	data = marshalMsg(msg)
	useBarMsg(fd, data)

	// baz
	fmt.Println("example of Baz ---")
	msg = makeBazMsg(fd)
	data = marshalMsg(msg)
	useBazMsg(fd, data)
}

参考

Go Protobuf APIv2 动态反射 Protobuf 使用指南

[译] Go 发布新版 Protobuf API