Go Opaque API: 手动迁移
Opaque API 是 Go 编程语言的 Protocol Buffers 实现的最新版本。旧版本现在称为 Open Struct API。请参阅Go Protobuf: 发布 Opaque API 博客文章以获取介绍。
这是一个用户指南,用于将 Go Protobuf 的用法从旧的 Open Struct API 迁移到新的 Opaque API。
生成代码指南提供了更多详细信息。本指南并排比较了旧 API 和新 API。
消息构造
假设有一个 protobuf 消息定义如下
message Foo {
uint32 uint32 = 1;
bytes bytes = 2;
oneof union {
string string = 4;
MyMessage message = 5;
}
enum Kind { … };
Kind kind = 9;
}
这是一个如何从字面值构造此消息的示例
| Open Struct API (旧) | Opaque API (新) |
| |
如您所见,构建器结构允许 Open Struct API (旧) 和 Opaque API (新) 之间几乎 1:1 的转换。
通常,为了可读性,更喜欢使用构建器。只有在极少数情况下,例如在热循环中创建 Protobuf 消息时,才可能更喜欢使用 setter 而不是构建器。有关更多详细信息,请参阅Opaque API FAQ: 我应该使用构建器还是 setter?。
上述示例的一个例外是处理 oneofs 时:Open Struct API (旧) 对每个 oneof 情况使用包装器结构类型,而 Opaque API (新) 将 oneof 字段视为常规消息字段
| Open Struct API (旧) | Opaque API (新) |
| |
对于与 oneof 联合关联的 Go 结构字段集,只能填充一个字段。如果填充了多个 oneof 情况字段,则最后一个(在 .proto 文件中的字段声明顺序)获胜。
标量字段
假设有一个定义了标量字段的消息
message Artist {
int32 birth_year = 1;
}
Go 使用标量类型(bool、int32、int64、uint32、uint64、float32、float64、string、[]byte 和 enum)的 Protobuf 消息字段将具有 Get 和 Set 访问器方法。具有显式存在的字段还将具有 Has 和 Clear 方法。
对于名为 birth_year 的 int32 类型字段,将为其生成以下访问器方法
func (m *Artist) GetBirthYear() int32
func (m *Artist) SetBirthYear(v int32)
func (m *Artist) HasBirthYear() bool
func (m *Artist) ClearBirthYear()
Get 返回字段的值。如果未设置字段或消息接收器为 nil,则返回默认值。默认值是零值,除非使用默认选项显式设置。
Set 将提供的值存储到字段中。在 nil 消息接收器上调用时会 panic。
对于字节字段,使用 nil []byte 调用 Set 将被视为已设置。例如,立即调用 Has 返回 true。立即调用 Get 将返回一个零长度切片(可以是 nil 或空切片)。用户应该使用 Has 来确定是否存在,而不依赖于 Get 是否返回 nil。
Has 报告字段是否已填充。在 nil 消息接收器上调用时返回 false。
Clear 清除字段。在 nil 消息接收器上调用时会 panic。
使用字符串字段的示例代码片段
| Open Struct API (旧) | Opaque API (新) |
| |
消息字段
假设有一个定义了消息类型字段的消息
message Band {}
message Concert {
Band headliner = 1;
}
消息类型的 Protobuf 消息字段将具有 Get、Set、Has 和 Clear 方法。
对于名为 headliner 的消息类型字段,将为其生成以下访问器方法
func (m *Concert) GetHeadliner() *Band
func (m *Concert) SetHeadliner(*Band)
func (m *Concert) HasHeadliner() bool
func (m *Concert) ClearHeadliner()
Get 返回字段的值。如果未设置或在 nil 消息接收器上调用时,它返回 nil。检查 Get 是否返回 nil 等效于检查 Has 是否返回 false。
Set 将提供的值存储到字段中。在 nil 消息接收器上调用时会 panic。使用 nil 指针调用 Set 等效于调用 Clear。
Has 报告字段是否已填充。在 nil 消息接收器上调用时返回 false。
Clear 清除字段。在 nil 消息接收器上调用时会 panic。
示例代码片段
| Open Struct API (旧) | Opaque (新) |
| |
重复字段
假设有一个定义了重复消息类型字段的消息
message Concert {
repeated Band support_acts = 2;
}
重复字段将具有 Get 和 Set 方法。
Get 返回字段的值。如果未设置字段或消息接收器为 nil,则返回 nil。
Set 将提供的值存储到字段中。在 nil 消息接收器上调用时会 panic。 Set 将存储提供的切片头的副本。切片内容的更改在重复字段中是可观察的。因此,如果使用空切片调用 Set,则立即调用 Get 将返回相同的切片。对于有线或文本封送输出,传入的 nil 切片与空切片无法区分。
对于消息 Concert 上名为 support_acts 的重复消息类型字段,将为其生成以下访问器方法
func (m *Concert) GetSupportActs() []*Band
func (m *Concert) SetSupportActs([]*Band)
示例代码片段
| Open Struct API (旧) | Opaque API (新) |
| |
映射
假设有一个定义了映射类型字段的消息
message MerchBooth {
map<string, MerchItems> items = 1;
}
映射字段将具有 Get 和 Set 方法。
Get 返回字段的值。如果未设置字段或消息接收器为 nil,则返回 nil。
Set 将提供的值存储到字段中。在 nil 消息接收器上调用时会 panic。 Set 将存储提供的映射引用的副本。提供的映射的更改在映射字段中是可观察的。
对于消息 MerchBooth 上名为 items 的映射字段,将为其生成以下访问器方法
func (m *MerchBooth) GetItems() map[string]*MerchItem
func (m *MerchBooth) SetItems(map[string]*MerchItem)
示例代码片段
| Open Struct API (旧) | Opaque API (新) |
| |
Oneof
对于每个 oneof 联合分组,消息上将有一个 Which、Has 和 Clear 方法。在该联合中的每个 oneof 情况字段上也将有一个 Get、Set、Has 和 Clear 方法。
假设有一个消息,其中定义了 oneof 字段 image_url 和 image_data 在 oneof avatar 中,如下所示
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
为该 oneof 生成的 Opaque API 将是
func (m *Profile) WhichAvatar() case_Profile_Avatar { … }
func (m *Profile) HasAvatar() bool { … }
func (m *Profile) ClearAvatar() { … }
type case_Profile_Avatar protoreflect.FieldNumber
const (
Profile_Avatar_not_set_case case_Profile_Avatar = 0
Profile_ImageUrl_case case_Profile_Avatar = 1
Profile_ImageData_case case_Profile_Avatar = 2
)
Which 通过返回字段编号报告设置了哪个情况字段。当未设置任何字段或在 nil 消息接收器上调用时返回 0。
Has 报告 oneof 中的任何字段是否已设置。在 nil 消息接收器上调用时返回 false。
Clear 清除 oneof 中当前设置的情况字段。在 nil 消息接收器上调用时会 panic。
为每个 oneof 情况字段生成的 Opaque API 将是
func (m *Profile) GetImageUrl() string { … }
func (m *Profile) GetImageData() []byte { … }
func (m *Profile) SetImageUrl(v string) { … }
func (m *Profile) SetImageData(v []byte) { … }
func (m *Profile) HasImageUrl() bool { … }
func (m *Profile) HasImageData() bool { … }
func (m *Profile) ClearImageUrl() { … }
func (m *Profile) ClearImageData() { … }
Get 返回情况字段的值。如果未设置情况字段或在 nil 消息接收器上调用时,它将返回零值。
Set 将提供的值存储到情况字段中。它还隐式清除 oneof 联合中以前填充的情况字段。使用 nil 值调用 oneof 消息情况字段上的 Set 会将字段设置为空消息。在 nil 消息接收器上调用时会 panic。
Has 报告情况字段是否已设置。在 nil 消息接收器上调用时返回 false。
Clear 清除情况字段。如果以前已设置,则 oneof 联合也会被清除。如果 oneof 联合设置为不同的字段,它将不会清除 oneof 联合。在 nil 消息接收器上调用时会 panic。
示例代码片段
| Open Struct API (旧) | Opaque API (新) |
| |
反射
在 proto 消息类型上使用 Go reflect 包访问结构字段和标签的代码在从 Open Struct API 迁移后将不再起作用。代码将需要迁移到使用 protoreflect
一些常用库在底层确实使用了 Go reflect,例如
- encoding/json
- pretty
- cmp
- 要正确使用
cmp.Equal与 protobuf 消息,请使用 protocmp.Transform
- 要正确使用