- Protocol Buffers 教程
- Protocol Buffers - 首页
- Protocol Buffers - 简介
- Protocol Buffers - 基本应用
- Protocol Buffers - 结构
- Protocol Buffers - message
- Protocol Buffers - 字符串
- Protocol Buffers - 数字
- Protocol Buffers - 布尔值
- Protocol Buffers - 枚举
- Protocol Buffers - 重复字段
- Protocol Buffers - map
- Protocol Buffers - 嵌套类
- Protocol Buffers - 可选字段与默认值
- Protocol Buffers - 语言无关性
- Protocol Buffers - 复合数据类型
- Protocol Buffers - 命令行使用
- Protocol Buffers - 更新定义规则
- Protocol Buffers - 与 Kafka 集成
- Protocol Buffers - 其他语言支持
- Protocol Buffers 有用资源
- Protocol Buffers - 快速指南
- Protocol Buffers - 有用资源
- Protocol Buffers - 讨论
Protocol Buffers - 复合数据类型
概述
还有两种复合数据类型可能对复杂的用例很有用。它们是“oneof”和“any”。在本章中,我们将学习如何使用这两种 Protobuf 数据类型。
oneof
我们将一些参数传递给此oneof数据类型,Protobuf确保只有一个参数被设置。如果我们设置其中一个,并尝试设置另一个,第一个属性将被重置。让我们通过一个例子来理解这一点。
继续我们来自Protocol Buffers - 字符串章节的theater示例,假设我们有一个用于获取可用员工数量的API。从该API返回的值随后被设置为以下文件中的'count'标签。但是,如果该API出错,我们无法真正获得'count',而是附加错误日志。
理想情况下,我们总是会设置其中一个,即调用成功并且我们得到计数,或者计数计算失败并且我们得到错误消息。
以下是我们需要使用的语法,以指示 Protobuf 我们将创建一个oneof属性:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
oneof availableEmployees {
int32 count = 4;
string errorLog = 5;
}
}
使用Java进行序列化
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录的com > tutorialspoint > theater文件夹中创建一个TheaterOuterClass.java类。我们像在Protocol Buffers - 基本应用章节中所做的那样,在我们的应用程序中使用此类。
使用从Proto文件创建的Java类
首先,我们将创建一个写入器来写入剧院信息:
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setCount(3)
.setErrorLog("No employee found")
.build();
String filename = "theater_protobuf_output";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
接下来,我们将有一个读取器来读取剧院信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println(theater);
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化Java对象
现在,编译后,让我们首先执行写入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: errorLog: "No employee found"
反序列化序列化对象
现在,让我们执行读取器从同一文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output errorLog: "No employee found"
因此,正如我们所看到的,我们只能看到后来设置的错误日志。
Any
下一个可用于复杂用例的数据类型是Any。我们可以将任何类型/消息/类传递给此数据类型,Protobuf 不会报错。让我们通过一个例子来理解这一点。
继续使用剧院示例,假设我们想要跟踪剧院内的人员。其中一些可能是员工,另一些可能是观众。但最终他们都是人,所以我们将把他们放在一个包含两种类型的列表中。
现在我们的类/消息包含一个Any属性'peopleInside'列表以及Viewer和Employee类,即剧院内人员的信息。让我们看看它的实际应用。
以下是我们需要使用的语法,以指示 Protobuf 我们将创建一个Any属性:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
import "google/protobuf/any.proto";
message Theater {
string name = 1;
string address = 2;
repeated google.protobuf.Any peopleInside = 3;
}
message Employee{
string name = 1;
string address = 2;
}
message Viewer{
string name = 1;
int32 age = 2;
string sex = 3;
}
使用Java进行序列化
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录的com > tutorialspoint > theater文件夹中创建一个TheaterOuterClass.java类。我们像在Protocol Buffers - 基本应用章节中所做的那样,在我们的应用程序中使用此类。
使用从Proto文件创建的Java类
首先,我们将创建一个写入器来写入剧院信息:
TheaterWriter.java
package com.tutorialspoint.theater;
import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
List<Any> people = new ArrayList<>();
people.add(Any.pack(Employee.newBuilder().setName("John").build()));
people.add(Any.pack(Viewer.newBuilder().setName("Jane").setAge(30).build()));
people.add(Any.pack(Employee.newBuilder().setName("Simon").build()));
people.add(Any.pack(Viewer.newBuilder().setName("Janice").setAge(25).build()));
Theater theater = Theater.newBuilder()
.setName("SilverScreen")
.addAllPeopleInside(people)
.build();
String filename = "theater_protobuf_output_silver";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
接下来,我们将有一个读取器来读取剧院信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.IOException;
import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output_silver";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println("Name:" + theater.getName() + "\n");
for (Any anyPeople : theater.getPeopleInsideList()) {
if(anyPeople.is(Employee.class)) {
Employee employee = anyPeople.unpack(Employee.class);
System.out.println("Employee:" + employee + "\n");
}
if(anyPeople.is(Viewer.class)) {
Viewer viewer = anyPeople.unpack(Viewer.class);
System.out.println("Viewer:" + viewer + "\n");
}
}
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化Java对象
现在,编译后,让我们首先执行写入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter
Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "SilverScreen"
peopleInside {
type_url: "type.googleapis.com/theater.Employee"
value: "\n\004John"
}
peopleInside {
type_url: "type.googleapis.com/theater.Viewer"
value: "\n\004Jane\020\036"
}
peopleInside {
type_url: "type.googleapis.com/theater.Employee"
value: "\n\005Simon"
}
peopleInside {
type_url: "type.googleapis.com/theater.Viewer"
value: "\n\006Janice\020\031"
}
注意 - 需要注意两点:
对于Any,Protobuf 将任何标签内的内容打包/序列化为字节,然后将其存储为'value'。基本上,这允许我们使用此'Any'标签发送任何消息类型。
我们还看到了"type.googleapis.com/theater.Viewer"和"type.googleapis.com/theater.Employee"。Protobuf 使用它来保存对象类型以及数据,因为Any数据类型的类型可能有所不同。
反序列化序列化对象
现在让我们执行读取器从同一文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output_silver Name:SilverScreen Employee:name: "John" Viewer:name: "Jane" age: 30 Employee:name: "Simon" Viewer:name: "Janice" age: 25
因此,正如我们所看到的,我们的读取器代码能够成功地区分Employee和Viewer,即使它们出现在同一个数组中。