Protocol Buffers是结构化数据格式标准,提供序列化和反序列方法,用于存储和交换。语言中立,平台无关、可扩展。目前官方提供了C++、Java、Python API,也有其他语言的开源api(比如php)。可通过 .proto文件生成对应语言的类代码。
如果已知protobuf内容对应的是哪个类对象,则可以直接使用反序列化方法搞定(Xxx.parseFrom(inputStream)由二进制转换,TextFormat.merge(string, xxxBuilder)由文本转换)。
而我们经常遇到的情况是,拿到一个被protobuf序列化的二进制内容,但不知道它的类型,无法获得对应的类对象。这种多见于需要处理各种各样未知的ProtoBuf对象的系统。ProtoBuf提供了动态解析机制来解决这个问题,它要求提供二进制内容的基础上,再提供对应类的Descriptor对象,在解析时通过DynamicMessage类的成员方法来获得对象结果。
最后问题就是Descriptor对象从哪里来?这是通过protoc --descriptor_set_out=$outputpath 命令生成descriptor文件,进而得到的。接下来,我们看一些例子(使用proto3)
maven:
com.google.protobuf
protobuf-java
3.0.2
1、使用protobuf的动态解析机制,来处理消息:
所谓动态解析,就是消费者不根据proto文件编译生成的类来反序列化消息,而是通过proto文件生成的descriptor来构造动态消息类,然后反序列化(解析)消息。代码如下:
1)cinema1.proto proto文件:
syntax = "proto3";
option java_package="com.lanjingling.cinema";
enum MovieType{
CHILDREN=0;
ADULT=1;
NORMAL=2;
OHTER=3;
}
enum Gender{
MAN=0;
WOMAN=1;
OTHER=2;
}
message Movie{
string name=1;
MovieType type=2;
int32 releaseTimeStamp=3;
string description=4;
string address=5;
}
message Customer{
string name=1;
Gender gender=2;
int32 birthdayTimeStamp=3;
}
message Ticket{
int32 id=1;
repeated Movie movie=2;
Customer customer=3;
}
2)编译proto文件:
//生成java源文件
D:\>protoc.exe --java_out=./ ./cinema1.proto
//生成descriptor,这里通过命令行延时如何生成的,下面的java代码里每次会通过命令生成
D:\>protoc.exe --descriptor_set_out=D://cinema1.description D://cinema1.proto --prot
o_path=D://
注:生成的descriptor文件也是一个二进制文件。
3)java代码:
public class Test1 {
public static void main(String[] args) throws Exception {
while (true) {
test();
break;
}
}
private static void test() throws IOException, InterruptedException,
FileNotFoundException, DescriptorValidationException,
InvalidProtocolBufferException {
System.out.println("------init msg------");
byte[] byteArray = initMsg();
System.out.println("------generate descriptor------");
// 生成descriptor文件
String protocCMD = "protoc --descriptor_set_out=D://cinema1.description D://cinema1.proto --proto_path=D://";
Process process = Runtime.getRuntime().exec(protocCMD);
process.waitFor();
int exitValue = process.exitValue();
if (exitValue != 0) {
System.out.println("protoc execute failed");
return;
}
System.out.println("------customer msg------");
Descriptors.Descriptor pbDescritpor = null;
DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet
.parseFrom(new FileInputStream("D://cinema1.description"));
for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fdp, new Descriptors.FileDescriptor[] {});
for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
String className = fdp.getOptions().getJavaPackage() + "."
+ fdp.getOptions().getJavaOuterClassname() + "$"
+ descriptor.getName();
System.out.println(descriptor.getFullName() + " -> "+ className);
if (descriptor.getName().equals("Ticket")) {
System.out.println("Movie descriptor found");
pbDescritpor = descriptor;
break;
}
}
}
if (pbDescritpor == null) {
System.out.println("No matched descriptor");
return;
}
DynamicMessage.Builder pbBuilder = DynamicMessage.newBuilder(pbDescritpor);
Message pbMessage = pbBuilder.mergeFrom(byteArray).build();
//DynamicMessage parseFrom = DynamicMessage.parseFrom(pbDescritpor, byteArray);
System.out.println(pbMessage);
}
private static byte[] initMsg() {
Cinema1.Movie.Builder movieBuilder = Cinema1.Movie.newBuilder();
movieBuilder.setName("The Shining");
movieBuilder.setType(Cinema1.MovieType.ADULT);
movieBuilder.setReleaseTimeStamp(327859200);
Movie movie = movieBuilder.build();
// byte[] byteArray = movie.toByteArray();
Cinema1.Movie.Builder movieBuilder1 = Cinema1.Movie.newBuilder();
movieBuilder1.setName("The Shining1");
movieBuilder1.setType(Cinema1.MovieType.CHILDREN);
movieBuilder1.setReleaseTimeStamp(327859201);
Movie movie1 = movieBuilder1.build();
Cinema1.Customer.Builder customerBuilder = Cinema1.Customer.newBuilder();
customerBuilder.setName("echo");
customerBuilder.setGender(Cinema1.Gender.MAN);
customerBuilder.setBirthdayTimeStamp(1231232333);
Cinema1.Ticket.Builder ticketBuilder = Cinema1.Ticket.newBuilder();
ticketBuilder.setId(1);
ticketBuilder.addMovie(movie);
ticketBuilder.addMovie(movie1);
ticketBuilder.setCustomer(customerBuilder.build());
Ticket ticket = ticketBuilder.build();
System.out.println(ticket.toString());
byte[] byteArray = ticket.toByteArray();
return byteArray;
}
}
4)代码整体流程如下:
5)优点:
生产者和消费者实现了解耦,消费者不再需要proto文件,而是使用proto文件生成的descriptor文件构造DynamicMessage类来解析生产者消息,一般我们可以把descriptor文件保存到某个地方,而不是每次都去生成。
2、自描述消息:
上面那种方式不太友好,消费者每次都需要重新生成或者从某个地方获取descriptor文件内容,google提供了一个更为合理的方式Self-describing Messages,即:将descriptor、消息名、消息内容放到一个wrapper message中,消费者收到消息后从wrapper message中先获取descriptor和name,然后构造DynamicMessage类,最后通过它来解析消息内容。
注:在protobuf库中,已经定义好了一些消息,如:descriptor/wrappers.proto等,我们直接引入即可。
1)selfmd.proto 文件:
syntax = "proto3";
option java_package="com.lanjingling.selfmd";
import "google/protobuf/descriptor.proto";
message SelfDescribingMessage {
// Set of FileDescriptorProtos which describe the type and its dependencies.
google.protobuf.FileDescriptorSet descriptor_set = 1;
string msg_name=2;
// The message and its type, encoded as an Any message.
bytes message = 3;
}
用来定义通用的消息体,里面包含了:descriptor、msg_name、message
2)cinema1.proto proto文件:
同上
3)编译cinema1.proto 文件:
同上,只需编译成java源文件即可,为了发送消息使用。(消费端不需要该java源文件,也不需要再生成descriptor了)
4)java代码:
public class Test2 {
public static void main(String[] args) throws Exception {
Ticket initMsg = initMsg();
System.out.println(initMsg);
//product
System.out.println("--------------protuct--------------");
DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet
.parseFrom(new FileInputStream("D://cinema1.description"));
Builder selfmdBuilder = Selfmd.SelfDescribingMessage.newBuilder();
selfmdBuilder.setDescriptorSet(descriptorSet);
selfmdBuilder.setMsgName(Ticket.getDescriptor().getFullName());
selfmdBuilder.setMessage(initMsg.toByteString());
SelfDescribingMessage build = selfmdBuilder.build();
byte[] byteArray = build.toByteArray();
//customer
System.out.println("--------------customer--------------");
SelfDescribingMessage parseFrom = Selfmd.SelfDescribingMessage.parseFrom(byteArray);
FileDescriptorSet descriptorSet2 = parseFrom.getDescriptorSet();
ByteString message = parseFrom.getMessage();
String msgName = parseFrom.getMsgName();
Descriptors.Descriptor pbDescritpor = null;
for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet2
.getFileList()) {
Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor
.buildFrom(fdp, new Descriptors.FileDescriptor[] {});
for (Descriptors.Descriptor descriptor : fileDescriptor
.getMessageTypes()) {
if (descriptor.getName().equals(msgName)) {
System.out.println("descriptor found");
pbDescritpor = descriptor;
break;
}
}
}
if (pbDescritpor == null) {
System.out.println("No matched descriptor");
return;
}
DynamicMessage dmsg = DynamicMessage.parseFrom(pbDescritpor, message);
System.out.println(dmsg);
}
public static Ticket initMsg() {
//同上
Ticket ticket = ticketBuilder.build();
return ticket;
}
}
5)代码整理流程:
注:这里没有找到通过protobuf的api方式生成descriptor文件内容的,网上都是通过protobuf提供的命令行工具来生成;
6)优点: