前言
暴雪出品,必属精品,google亦然。对象序列化方式有很多,个人而言,java自带序列化ObjectOutputStream/ObjectOutputStream,我只是写过一些UT,实际商业项目中从没用过。Json用的最多,而且Json现在的流行度跟很早之前的XML一样都很高,各种rpc框架基本都会支持。最近遇到一个java 调用c++服务的项目,用到了google的protobuf,算是我的一个盲区,写一篇文章记录一下学习过程。主要参见官网文档及demo https://developers.google.cn/protocol-buffers/
protobuf 是什么
Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data,think XML, but smaller, faster, and simpler.
官网介绍简明扼要,protobuf就是一个序列化框架,语言无关,平台无关,可拓展;比XML性能更好更易用。一次定义,到处运行。
protobuf使用流程
Define message formats in a .proto file.
Use the protocol buffer compiler.
Use the Java protocol buffer API to write and read messages.
1.定义一个.proto文件
2.使用protobuf的编译器对.proto文件进行编译,生成语言相关的类。
3.使用生成的类,通过protobuf api进行读写
以官方demo中的addressbook.proto文件为例
syntax = "proto2"; //定义protobuf版本,可以是proto2或者proto3
package tutorial;
option java_package = "com.example.tutorial"; //输出类文件的package
option java_outer_classname = "AddressBookProtos";//类文件名
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
.proto文件的核心应该是message的定义,类似C语言的结构体。
比如上例,Person有四个属性,name,id,email,phones
protobuf内置了bool, int32, float, double, and string等基本类型,大眼一看也能知道对应java的什么数据类型。属性也支持非基本类型之外的复杂结构,比如PhoneNumber phones属性,他本身也是一个message结构体,跟java的对象属性一样。
注意的点,上述.proto文件中定义的属性后面都有 =1,=2,=3,=4 这些,这不是属性初始化的默认值,这是属性的tag,是一个属性的标识,是一个非常关键的概念,这个tag主要是用来支持.proto文件的拓展。比如我们server进行了升级,加了字段,那么我们的consumer端,是不是一定也要同步升级呢?其实不用,consumer忽略你加的字段即可,这个特性,与这个tag是强相关的。其实现原理是,序列化时,protobuf将属性存为一个键值对,这个key,即属性的tag号,那么反序列化的时候,consumer就可以忽略不识别的key,只解析那些自己识别的key。
上述.proto文件定义好之后,就可以使用protobuf的编译器protoc对文件进行编译了
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
这个命令只要三个参数,.proto文件目录,输出类文件目录,.proto文件全路径
比如我本机
protoc -I=/Users/wuzhiwei/tianchi/javacase/java_case/src/main/java/protobuf --java_out=/Users/wuzhiwei/tianchi/javacase/java_case/src/main/java /Users/wuzhiwei/tianchi/javacase/java_case/src/main/java/protobuf/addressbook.proto
执行成功之后,会在输出类文件目录中生成对应的类文件,AddressBookProtos.java
生成的代码太长就不贴了,跟thrift类似,都是固定的套路对属性生成get,set方法,以及其他的一些api,感兴趣完整版又不愿动手的可以到我的git上面拿代码https://github.com/somewaters/java_case/tree/master/src/main/java/protobuf
有一点,生成的文件特别提到了,生成的类文件不要edit,这个说的其实是不要改变那些与.proto文件对应的属性和方法,本质上其实这还是一个java类文件,你可以在他生成的类文件中加入自己想要的方法或者属性,并不影响功能正常使用。
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: addressbook.proto
类文件生成完毕,protobuf api有两个关键的方法,toByteArray,parseFrom分别来支持对象序列化和反序列化
public byte[] toByteArray() {
try {
byte[] result = new byte[this.getSerializedSize()];
CodedOutputStream output = CodedOutputStream.newInstance(result);
this.writeTo(output);
output.checkNoSpaceLeft();
return result;
} catch (IOException var3) {
throw new RuntimeException(this.getSerializingExceptionMessage("byte array"), var3);
}
}
public static protobuf.AddressBookProtos.AddressBook parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
同样的完整例子可以看之前我的git代码。
使用层面的话,protobuf api还是很丰富的,我只关注与对象跟byte[]之前的转化。
注意的点,之前提过,protobuf支持可拓展,但是这个拓展是有前提的。
you must not change the tag numbers of any existing fields.
you must not add or delete any required fields.
you may delete optional or repeated fields.
you may add new optional or repeated fields but you must use fresh tag numbers (i.e. tag numbers that were never used in this protocol buffer, not even by deleted fields).
1.不能改变属性的tag值。(就是之前.proto文件中定义属性的时候=1,=2那些值)
2.required修饰的属性不能加或者删
2.optional or repeated修饰的属性可以删
4.optional or repeated修饰的属性可以加,但是必须使用全新的tag号。
总结:
语言无关,平台无关,依赖的是二进制流先天的跨平台,跨语言,protobuf提供不同语言,不同平台的的api负责将对象序列化为byte[]或者将byte[]反序列化即可;
一次编写,到处运行,protobuf定义好.proto文件后,在所有地方,都是通过其compiler 自动生成的类文件,编写一次.proto文件即可。
可拓展,.proto文件在遵循拓展规则之后,可以做到兼容升级,老版本忽略新版本的增加的内容即可。
所有的序列化框架,做的都是同样的事情,即将对象序列化为byte[],或者反之。那么为什么protobuf就敢号称faster呢?后续如果有必要,会继续研究其在性能上取胜到底依赖的是什么。
网友评论