一、什么是protobuf
Protobuf(Protocol Buffers)是由Google开发的一种灵活、高效、自动化的结构化数据序列化方法,类似于XML、JSON等,但更小、更快、更简单。它主要用于网络通信和数据存储等场景,广泛应用于各种编程语言中。
Protobuf的作用主要体现在以下几个方面:
-
数据序列化与反序列化 :
Protobuf可以将数据结构或对象序列化为一个二进制流,同时也可以将这个二进制流反序列化回原始数据结构。这种序列化方式相比于传统的文本格式(如XML、JSON)更加高效,因为它采用了二进制格式,减少了存储空间和网络传输的带宽消耗。
-
跨语言通信 :
Protobuf支持多种编程语言,包括C++、Java、Python等。这意味着在不同编程语言编写的程序之间,可以使用Protobuf格式进行高效的数据交换和通信。
-
向后兼容性 :
Protobuf设计时就考虑到了向后兼容性。当数据结构发生变化时(例如添加新的字段),旧的程序仍然可以读取由新程序序列化的数据,忽略新增的字段。
-
自动化处理 :
Protobuf编译器(protoc)可以根据.proto文件(定义数据结构的文本文件)自动生成对应语言的源代码,这些源代码提供了序列化、反序列化以及数据访问的方法,极大地简化了开发过程。
二、基本使用步骤
1、在pom.xml中添加编译插件和相关依赖
xml
<properties>
<!--<os.detected.classifier>windows-x86_64</os.detected.classifier>-->
<protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
<protoc.version>3.25.3</protoc.version>
<protobuf.java.version>3.25.3</protobuf.java.version>
<grpc-java.version>1.64.0</grpc-java.version>
</properties>
//...//
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
</dependencies>
// ... //
<build>
<pluginManagement>
<plugins>
<!-- protobuf-maven-plugin -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>protoc-compile</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
<execution>
<id>protoc-test-compile</id>
<phase>generate-test-sources</phase>
<goals>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
</protocArtifact>
<attachProtoSources>true</attachProtoSources>
<useArgumentFile>true</useArgumentFile>
<writeDescriptorSet>false</writeDescriptorSet>
<attachDescriptorSet>false</attachDescriptorSet>
<includeDependenciesInDescriptorSet>false</includeDependenciesInDescriptorSet>
<checkStaleness>true</checkStaleness>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${grpc-java.version}:exe:${os.detected.classifier}
</pluginArtifact>
<protocPlugins>
</protocPlugins>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、在工程目录src/main/proto
目录中创建 person.proto
插件固定编译src/main/proto
的.proto
文件,所以文件必须放在这里
java
syntax = "proto3";
option java_package = "com.demo.proto";
option java_multiple_files = true;
option java_outer_classname = "UserProto";
message User {
string name = 1;
string email = 2;
int32 age = 3;
}
参数说明
java_package
:生成的Java文件对应的包名java_multiple_files
:将实体、proto、builder类放在不同文件中java_outer_classname
:指定生成的proto文件名称,如果没指定就是OuterClass结尾(如UserOuterClass)
3、执行 Maven compile
编译工程生成代码
插件会根据proto文件自动在target/classes
目录中生成Java文件
- com.demo.proto.User
- com.demo.proto.UserOrBuilder
- com.demo.proto.UserProto
4、编写测试类
java
public class UserProtoTest {
@Test
public void test() throws InvalidProtocolBufferException {
User.Builder builder = User.newBuilder();
builder.setName("jacky");
builder.setAge(30);
builder.setEmail("jacky@example.com");
User user = builder.build();
// 序列化
byte[] serializedUser = user.toByteArray();
// 反序列化
Person deserializedUser = Person.parseFrom(serializedUser);
System.out.println("--------deserialized--------");
System.out.println(deserializedUser);
}
}
执行结果如下:
--------deserialized--------
name: "jacky"
email: "jacky@example.com"
age: 30
三、proto文件,字段类型和java类的对照
在Protocol Buffers(简称Proto)中,消息字段类型与Java类型之间存在一定的对应关系。以下是一个表格,列出了Proto中的常见消息字段类型及其在Java中的对应类型:
Proto字段类型 | Java类型 | 备注 |
---|---|---|
double |
double |
双精度浮点数 |
float |
float |
单精度浮点数 |
int32 |
int |
使用可变长度编码,对于有符号整数,如果你的字段可能包含负数,则建议使用sint32 |
sint32 |
int |
使用可变长度编码,有符号整数 |
uint32 |
int |
使用可变长度编码,无符号整数 |
int64 |
long |
使用可变长度编码,对于有符号整数,如果你的字段可能包含负数,则建议使用sint64 |
sint64 |
long |
使用可变长度编码,有符号整数 |
uint64 |
long |
使用可变长度编码,无符号整数 |
fixed32 |
int |
总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32 更高效 |
fixed64 |
long |
总是8个字节,如果数值总是比2^56大的话,这个类型会比uint64 更高效 |
sfixed32 |
int |
总是4个字节 |
sfixed64 |
long |
总是8个字节 |
bool |
boolean |
布尔值 |
string |
String |
字符串,UTF-8编码 |
bytes |
ByteString |
字节序列 |
注意:
-
对于整数类型,Proto中的
int32
和int64
类型在Java中分别对应int
和long
类型,但由于Java没有无符号整数类型,因此在使用时需要注意。对于有符号整数且可能包含负值的情况,建议使用sint32
和sint64
类型,它们在编码时会处理符号位。 -
fixed32
和fixed64
类型分别总是占用4个字节和8个字节的空间,无论其实际值的大小如何。这在某些情况下可以提高编码效率,特别是当你知道数值范围时。 -
sfixed32
和sfixed64
类型也分别占用固定的4个字节和8个字节空间,但它们用于有符号整数。 -
string
类型在Java中对应String
类,而bytes
类型则对应ByteString
类,后者是Protobuf提供的一个专门用于处理字节序列的类。
四、proto文件语法
.proto
文件,用于定义数据结构和服务接口。以下是.proto
文件的语法详解:
1. 文件结构
.proto
文件由一系列定义组成,包括语法声明、包声明、导入声明、消息类型定义、枚举类型定义、服务接口定义以及选项声明。
2. 语法声明
语法声明用于指定.proto
文件使用的Proto版本。例如:
proto
syntax = "proto3";
目前常用的版本有proto2
和proto3
,其中proto3
是更现代、更简洁的语法。
3. 包声明
包声明用于指定.proto
文件中定义的类型所属的命名空间。例如:
proto
package example;
在Proto中,包名用于避免不同.proto
文件中定义的类型之间的名称冲突。
4. 导入声明
导入声明用于导入其他.proto
文件中定义的类型。例如:
proto
import "other_proto_file.proto";
导入声明使得当前.proto
文件可以使用其他文件中定义的消息类型、枚举类型和服务接口。
5. 消息类型定义
消息类型定义用于定义数据结构。例如:
proto
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
在消息类型定义中,每个字段都有一个类型、一个名称和一个唯一的编号。字段编号用于在序列化过程中识别字段。
6. 枚举类型定义
枚举类型定义用于定义一组命名的整数常量。例如:
proto
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
枚举类型中的每个常量都有一个名称和一个整数值。第一个枚举值的整数值必须为0。
7. 服务接口定义
服务接口定义用于定义RPC(远程过程调用)方法。例如:
proto
service AddressBook {
rpc GetPerson(Person) returns (Person);
}
在服务接口定义中,每个RPC方法都有一个请求类型、一个响应类型和一个方法名。请求类型和响应类型都是之前定义的消息类型。
8. 选项声明
选项声明用于设置文件级别的选项,如生成代码的特定语言设置。例如:
proto
option java_package = "com.example.proto";
option java_outer_classname = "ProtoExample";
option java_multiple_files = true;
这些选项影响生成的代码,如Java代码的包名和外部类名。
总结
.proto
文件的语法相对简单,主要由一系列定义组成。通过定义消息类型、枚举类型和服务接口,.proto
文件描述了数据的结构和服务的接口。然后,可以使用Proto编译器将这些定义编译成各种语言的代码,以便在应用程序中使用。