Go Opaque API:手动迁移
Opaque API 是 Go 编程语言 Protocol Buffers 实现的最新版本。旧版本现在称为 Open Struct API。有关介绍,请参阅 Go Protobuf:发布 Opaque API 这篇博文。
本用户指南旨在帮助用户将 Go Protobuf 的使用从旧的 Open Struct API 迁移到新的 Opaque 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 (新) |
|
|
如您所见,构建器结构体 (builder struct) 使得从 Open Struct API(旧)到 Opaque API(新)的转换几乎可以 1:1 实现。
通常,为了可读性,应优先使用构建器。只有在极少数情况下,例如在性能敏感的内部循环中创建 Protobuf 消息时,才可能更适合使用 setter 而非构建器。有关更多详细信息,请参阅Opaque API 常见问题解答:我应该使用构建器还是 setter?
上述示例的一个例外是处理 oneof 时:Open Struct API(旧)为每个 oneof case 使用一个包装器结构体类型,而 Opaque API(新)则将 oneof 字段视为常规消息字段。
Open Struct API (旧) | Opaque API (新) |
|
|
对于与一个 oneof 联合关联的 Go 结构体字段集,只有一个字段可以被填充。如果填充了多个 oneof case 字段,则最后一个(在 .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,则返回默认值。默认值是零值,除非使用 default 选项明确设置。
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 (新) |
|
|
映射
假设有一个定义了 map 类型字段的消息:
message MerchBooth {
map<string, MerchItems> items = 1;
}
Map 字段将具有 Get
和 Set
方法。
Get
返回字段的值。如果字段未设置或消息接收者为 nil,则返回 nil。
Set
将提供的值存入字段。在 nil 消息接收者上调用时会引发 panic。Set
将存储所提供的 map 引用的副本。对所提供 map 的更改在 map 字段中是可见的。
对于 MerchBooth
消息上一个名为 items
的 map 字段,将为其生成以下访问器方法:
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 case 字段也将有一个 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
通过返回字段编号来报告设置了哪个 case 字段。当没有设置任何字段或在 nil 消息接收者上调用时,它返回 0。
Has
报告 oneof 中的任何字段是否已设置。在 nil 消息接收者上调用时返回 false。
Clear
清除 oneof 中当前设置的 case 字段。在 nil 消息接收者上调用时会引发 panic。
为每个 oneof case 字段生成的 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
返回 case 字段的值。如果 case 字段未设置或在 nil 消息接收者上调用,它将返回零值。
Set
将提供的值存入 case 字段。它还会隐式清除 oneof 联合中先前填充的 case 字段。对 oneof 消息 case 字段使用 nil 值调用 Set
将把该字段设置为空消息。在 nil 消息接收者上调用时会引发 panic。
Has
报告 case 字段是否已设置。在 nil 消息接收者上调用时返回 false。
Clear
清除 case 字段。如果它先前已设置,则 oneof 联合也会被清除。如果 oneof 联合设置了不同的字段,它将不会清除该 oneof 联合。在 nil 消息接收者上调用时会引发 panic。
示例代码片段
Open Struct API (旧) | Opaque API (新) |
|
|
反射
在从 Open Struct API 迁移后,使用 Go reflect
包访问 proto 消息类型的结构体字段和标签的代码将不再起作用。代码需要迁移到使用 protoreflect。
一些常用库在底层确实使用了 Go reflect
,例如:
- encoding/json
- pretty
- cmp
- 要正确地对 protobuf 消息使用
cmp.Equal
,请使用 protocmp.Transform。
- 要正确地对 protobuf 消息使用