private static final com.google.protobuf.Parser<Person>
PARSER = new com.google.protobuf.AbstractParser<Person>() {
@java.lang.Override
public Person parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new Person(input, extensionRegistry);
}
};
4).Builder类
Builder类为Person的内部类,一样实现了PersonOrBuilder接口,不过额外定义了set的方法
public static final class Builder extends
com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
// @@protoc_insertion_point(builder_implements:Person)
cn.tera.protobuf.model.BasicUsage.PersonOrBuilder {
...
}
这里的get方法的逻辑和Person类一样,不过特别注意的是,这里的name_和Person的getName方法中的name_不是同一个对象,而是分别属于Builder类和Person类的private字段
public java.lang.String getName() {
java.lang.Object ref = name_;
if (!(ref instanceof java.lang.String)) {
com.google.protobuf.ByteString bs =
(com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
name_ = s;
return s;
} else {
return (java.lang.String) ref;
}
}
查看set方法,比较简单,就是一个直接的赋值操作
public Builder setName(
java.lang.String value) {
if (value == null) {
throw new NullPointerException();
}
name_ = value;
onChanged();
return this;
}
最后,我们来看下Builder的build方法,这里调用了buildPartial方法
@java.lang.Override
public cn.tera.protobuf.model.BasicUsage.Person build() {
cn.tera.protobuf.model.BasicUsage.Person result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
查看buildPartial方法,可以看到这里调用了Person获取builder参数的构造函数,和前文对应
构造完成后,将Builder中的各种字段赋值给Person中的相应字段,即完成了构造
@java.lang.Override
public cn.tera.protobuf.model.BasicUsage.Person buildPartial() {
cn.tera.protobuf.model.BasicUsage.Person result = new cn.tera.protobuf.model.BasicUsage.Person(this);
result.name_ = name_;
result.id_ = id_;
result.email_ = email_;
onBuilt();
return result;
}
总结一下:
1.protocol buffer需要定义.proto描述文件,然后通过google提供的编译器生成特定的模型文件,之后就可以作为正常的java对象使用了
2.不可以直接创建对象,需要通过Builder进行
3.只有Builder才可以进行set
4.可以通过对象的toByteArray()和parseFrom()方法进行编码和解码
5.模型文件很大(至少在java这里是如此),其中所有的代码都是定制的,这其实是它很大的缺点之一
接着我们将继续深入探究protobuf的编码原理。
主要分为两个部分
第一部分是之前留下的几个伏笔展示protobuf的使用特性
第二部分是分析protobuf的编码原理,解释特性背后的原因
第一部分,Protobuf使用特性
1.不同类型对象的转换
我们先定义如下一个.proto文件
syntax = "proto3";
option java_package = "cn.tera.protobuf.model";
option java_outer_classname = "DifferentModels";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
message Article {
string title = 1;
int32 wordsCount = 2;
string author = 3;
}
其中我们定义了2个模型,一个Person,一个Article,虽然他们的字段名字不相同,但是类型和编号都是一致的
接着我们生成.java文件,最终文件结构如下图
此时我们尝试做如下的一个转换
/**
* 测试不同模型间的转换
* @throws Exception
*/
@Test
public void parseDifferentModelsTest() throws Exception {
//创建一个Person对象
DifferentModels.Person person = DifferentModels.Person.newBuilder()
.setName("person name")
.setId(1)
.setEmail("tera@google.com")
.build();
//对person编码
byte[] personBytes = person.toByteArray();
//将编码后的数据直接merge成Article对象
DifferentModels.Article article = DifferentModels.Article.parseFrom(personBytes);
System.out.println("article's title:" + article.getTitle());
System.out.println("article's wordsCount:" + article.getWordsCount());
System.out.println("article's author:" + article.getAuthor());
}
输出结果如下
article's title:person name
article's wordsCount:1
article's author:tera@google.com
可以看到,虽然jsonBytes是由person对象编码得到的,但是可以用于article对象的解码,不但不会报错,所有的数据内容都是完整保留的
这种兼容性的前提是模型中所定义的字段类型和序号都是一一对应相同的
在平时的编码中,我们经常会遇到从数据库中读取数据模型,然后将其转换成业务模型,而很多时候,这2种模型的内容其实是完全一致的,此时我们也许就可以使用protobuf的这种特性,就可以省去很多低效的赋值代码
2.protobuf序号的重要性
我们看到在定义.proto文件时,字段后面会跟着一个"= X",这里并不是指这个字段的值,而是表示这个字段的“序号”,和正确地编码与解码息息相关,在我看来是protocol buffer的灵魂
我们定义如下的.proto文件,这里注意,Model1和Model2的name和id的序号有不同
syntax = "proto3";
option java_package = "cn.tera.protobuf.model";
option java_outer_classname = "TagImpor