曹耘豪的博客

Protobuf 的一些坑

  1. 字段顺序问题

字段顺序问题

getDeclaredFields 方法返回的 Field 列表顺序和我们在类里面定义的不一样。它是做过字母排序的。

我们知道 Protobuf 的序列化中所需要的 Schema 是对类下面的 Field 顺序强依赖的。

而在我们的Server端的调式中, 我们发现我们的Field顺序是和我们在类中定义的是一样的。

JDK 1.6 以上这个顺序才保证是和类定义中的顺序是一致的,而在早期版本中这个顺序是没保证的,是根据各 JDK 的实现自己做的。也就是说 ProtostuffRuntime 其实只能保证在 JDK1.6 以上能正确运行。 更别说 Android 这样的非正统的 JVM 系统了。
问题找到了,那怎么解决呢?

我们知道 Protobuf 对兼容字段差异的兼容可以做到的是,如果有一个 Bean Class 有 10 个字段序列化方 S 方和反序列化方 DS 方的字段

假如有 BeanClass 在某一边做了升级,添加了第11个field,如果这第11个field在末尾,那么是没问题的。

如果在中间添加这个字段,那将会导致在另一边无法做反序列化。

从上面第三节里面,我们看 Protobuf 的生成 Protobuf Schema 的流程我们知道,

假如有个类ClassA extends SuperClassA

如果 ClassA 中有 FieldA1, FieldA2

SuperClassA 中有 FieldsSA1,FieldsSA2。

那么最终编译到 Schema 中的顺序会是

FieldsSA1,FieldsSA2, FieldA1, FieldA2.

如果我们对SuperClassA上新增FieldSA3,那么顺序会是

FieldsSA1,FieldsSA2, FieldsSA2, FieldA1, FieldA2

如果另外一端因为 Class 未升级, 那么编译的顺序还是FieldsSA1,FieldsSA2, FieldA1, FieldA2。那么将会导致无法正确反序列化。

用一句话总结就是,如果Super Class中新增字段了,必须两端程序同时升级。而如果在子类中新增字段,并且增加在字段列表中的最后一个,那么是不需要另外一端跟着升级的。

这里我们可以看到 Protobuf 的运行高效性所带来的一些问题。相应的这类问题是不会存在在使用 JSON 格式转换服务上。

如果需要自定义顺序,可以使用 @Tag(1) 注解来表明顺序,该注解从 1 开始。