Go Opaque API:手动迁移
Opaque API 是 Protocol Buffers 针对 Go 编程语言的最新实现版本。旧版本现在被称为 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 structs)使得从 Open Struct API (旧) 到 Opaque API (新) 的转换几乎可以一对一地完成。
通常,为了可读性,推荐使用构建器。只有在极少数情况下,比如在性能敏感的内层循环中创建 Protobuf 消息时,使用 setter 可能比使用构建器更可取。更多详情请参阅Opaque API 常见问题解答:我应该使用构建器还是 setter?。
上述示例的一个例外是处理 oneofs: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。
对于 bytes 字段,使用 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;
}
类型为 message 的 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 (新) |
| |
重复字段
假设有一个定义了 repeated 消息类型字段的消息:
message Concert {
repeated Band support_acts = 2;
}
Repeated 字段将拥有 Get 和 Set 方法。
Get 返回字段的值。如果字段未设置或消息接收者为 nil,则返回 nil。
Set 将提供的值存入字段。在 nil 消息接收者上调用时会引发 panic。Set 会存储所提供切片头的副本。对切片内容的更改在 repeated 字段中是可见的。因此,如果使用空切片调用 Set,紧接着调用 Get 将返回相同的切片。对于有线(wire)或文本(text)编组输出,传入的 nil 切片与空切片是无法区分的。
对于 Concert 消息上一个名为 support_acts 的 repeated 消息类型字段,将为其生成以下访问器方法:
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 消息使用