使用protobuf3踩过的那些坑(Java)
相对于 protobuf2,protobuf3 变化很大,尤其是默认值的变化给使用者带来很大不便。
默认值
protobuf3 删除了 protobuf2 中用来设置默认值的 default 关键字,取而代之的是protobuf3为各类型定义的默认值,也就是约定的默认值,如下表所示:
类型 | 默认值 |
---|---|
bool | false |
整型 | 0 |
string | 空字符串"" |
枚举enum | 第一个枚举元素的值,因为Protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0; |
message | 不是null,而是DEFAULT_INSTANCE |
可以看出来,protobuf3定义的默认值跟Java中类的属性的默认值规则并不一样:Java中,如果类的属性类型是类,则该属性默认值是null,而protobuf3中,string、message的默认值都不是null。
枚举enum类型:
1、不支持一个proto文件中,多个枚举中定义相同的枚举常量名。
如下的两个枚举,定义在同一个proto文件中:
enum Enum1 {
IDLE = 0;
RUNNING = 1;
}
enum Enum2 {
IDLE = 5;
RUNNING = 6;
}
编译时,会报出错误:IDLE is already defined in "xxx",出现这一错误的原因就是:Protobuf3中不允许同一proto中,多个枚举中使用相同的枚举值。
而有意思的是:在proto编译生成的Java文件中,protobuf自己却为每个枚举都添加了一个UNRECOGNIZED(值为-1),意味着protobuf不允许使用者为两个枚举添加相同的枚举值,却允许自己添加。。。
实际上,这一现象是有悖于Java开发者习惯的,因为在Java中,并不会对枚举有上述限制,在使用上会让人感觉很别扭,带着这个困惑,我提了个bug:
Can not define two same enum name in the same proto file
而官方的解答是:
1、设计如此,给名字一样的枚举值加个前缀来解决这个问题。。。
2、Protobuf要兼顾所有语言的特性,我试了一下:C语言也不不允许这么定义(以前真不知道这个情况,学艺不精呀。。)。
2、枚举第一个常量的值必须是0
实际项目中使用的枚举常量值经常是从0开始的,这样项目需求与protobuf3有冲突。
解决方法是,将第一个枚举常量0定义为无效值,或者额外定义一个无效值(比如-1),Google API Guider中建议枚举的第一个值为 ENUN_TYPE_UNSPECIFIED,即枚举名_UNSPECIFIED,举个例子:
enum BallTypeEnum {
BALL_TYPE_UNSPECIFIED = 0;
BASKETBALL = 1;
FOOTBALL = 2;
}
这样,让默认的枚举值与业务的相分离。
message类型:
Java中,message类型的默认值是DEFAULT_INSTANCE,其值相当于空的message,即XXX.newBuilder().build(),这样对message类型的判空操作就应该是这样:
// protobuf message
message User {
int32 id = 1;
string name = 2;
string email = 3;
Address address = 4;
}
message Address {
string street = 1;
string building = 2;
}
// Java
if (user.getAddress() != null && user.getAddress() != UserProto.Address.getDefaultInstance()) {
...
} else {
...
}
结语
尽管protobuf3有一些不方便的地方,但protobuf毕竟数据交换协议,负责交换数据,所以建议在使用protobuf时,不要与具体业务耦合过紧,protobuf相关操作放在数据传输的接口层。