Protobuf - 复合数据类型
还有两种复合数据类型可能对复杂的用例有用。它们是"OneOf"和"Any"。在本章中,我们将了解如何使用 Protobuf 的这两种数据类型。
OneOf
我们将一些参数传递给此OneOf数据类型,Protobuf 确保只设置其中一个。如果我们设置其中一个并尝试设置另一个,则第一个属性将被重置。让我们通过一个例子来理解这一点。
继续我们的剧院示例,例如,我们有一个用于获取可用员工数量的 API。然后,从此 API 返回的值在以下文件中设置为"count"标签。但是如果该 API 出错,我们就无法真正"计数",而是附加错误日志。
理想情况下,我们始终会设置其中一个,即,要么调用成功,我们得到计数,要么计数计算失败,我们得到错误消息。
以下是我们需要指示 Protobuf 创建 OneOf 属性的语法 −
syntax = "proto3"; package theater; option java_package = "com.tutorialspoint.theater"; message Theater { string name = 1; string address = 2; repeated google.protobuf.Any peopleInside = 3; oneof availableEmployees { int32 count = 4; string errorLog = 5; } }
现在我们的 class/message 包含一个 OneOf 属性,即有关可用员工的信息。
要使用 Protobuf,我们必须使用 protoc 二进制文件从此".proto"文件创建所需的类。让我们看看如何做到这一点 −
protoc --java_out=java/src/main/java proto_files heater_advanced.proto
上述命令应创建所需的文件,现在我们可以在 Java 代码中使用它。首先,我们将创建一个 writer 来写入 theater 信息 −
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.TheaterAdvanced.Employee; import com.tutorialspoint.theater.TheaterAdvanced.Viewer; import com.tutorialspoint.theater.TheaterAdvanced.Theater; public class TheaterWriterComplex{ 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: " + theater); } }
接下来,我们将有一个reader(阅读器)来读取theater(剧院)的信息 −
package com.tutorialspoint.theater; import java.io.FileInputStream; import java.io.IOException; import com.google.protobuf.Any; import com.tutorialspoint.theater.TheaterAdvanced.Theater; import com.tutorialspoint.theater.TheaterAdvanced.Theater.AvailableEmployeesCase; import com.tutorialspoint.theater.TheaterAdvanced.Theater.Builder; import com.tutorialspoint.theater.TheaterAdvanced.Viewer; import com.tutorialspoint.theater.TheaterAdvanced.Employee; public class TheaterReaderComplex{ 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() + " "); for (Any anyPeople : theater.getPeopleInsideList()) { if(anyPeople.is(Employee.class)) { Employee employee = anyPeople.unpack(Employee.class); System.out.println("Employee:" + employee + " "); } if(anyPeople.is(Viewer.class)) { Viewer viewer = anyPeople.unpack(Viewer.class); System.out.println("Viewer:" + viewer + " "); } } } } }
现在,编译后,让我们首先执行writer −
> java -cp . arget\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output_silver Saved theater information with following data to disk: name: "SilverScreen" peopleInside { type_url: "type.googleapis.com/theater.Employee" value: " \004John" } peopleInside { type_url: "type.googleapis.com/theater.Viewer" value: " \004Jane\020\036" } peopleInside { type_url: "type.googleapis.com/theater.Employee" value: " \005Simon" } peopleInside { type_url: "type.googleapis.com/theater.Viewer" value: " \006Janice\020\031" }
现在,让我们执行 reader 来读取同一个文件 −
java -cp . arget\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
因此,正如我们所见,在列表中,我们能够找出 Any 类型并找到相应的基础数据类型employee/viewer。 现在让我们看看defaults和AnyOf。
任何
可用于复杂用例的下一个数据类型是Any。 我们可以将任何类型/消息/类传递给该数据类型,Protobuf 不会抱怨。 让我们通过一个例子来理解这一点。
继续以剧院为例,我们想要跟踪剧院内的人员。 其中一些可能是员工,其他人可能是观众。但最终他们还是人,所以我们将把他们传递到一个包含这两种类型的列表中。
以下是我们需要用来指示 Protobuf 创建列表的语法 −
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; }
现在我们的 class/message 包含一个 Any 属性 'peopleInside' 列表以及 Viewer 和 Employee 类,即有关剧院内人员的信息。让我们看看实际操作。
要使用 Protobuf,我们现在必须使用 protoc 二进制文件从此".proto"文件创建所需的类。让我们看看如何做到这一点 −
protoc --java_out=java/src/main/java proto_files heater_advanced.proto
上述命令应创建所需的文件,现在我们可以在 Java 代码中使用它。首先,我们将创建一个 writer 来写入 theater 信息 −
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.TheaterAdvanced.Employee; import com.tutorialspoint.theater.TheaterAdvanced.Viewer; import com.tutorialspoint.theater.TheaterAdvanced.Theater; 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: " + theater); } }
接下来,我们将有一个reader(阅读器)来读取theater(剧院)的信息 −
package com.tutorialspoint.theater; import java.io.FileInputStream; import java.io.IOException; import com.google.protobuf.Any; import com.tutorialspoint.theater.TheaterAdvanced.Theater; import com.tutorialspoint.theater.TheaterAdvanced.Theater.AvailableEmployeesCase; import com.tutorialspoint.theater.TheaterAdvanced.Theater.Builder; import com.tutorialspoint.theater.TheaterAdvanced.Viewer; import com.tutorialspoint.theater.TheaterAdvanced.Employee; public class TheaterReaderComplex{ 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() + " "); for (Any anyPeople : theater.getPeopleInsideList()) { if(anyPeople.is(Employee.class)) { Employee employee = anyPeople.unpack(Employee.class); System.out.println("Employee:" + employee + " "); } if(anyPeople.is(Viewer.class)) { Viewer viewer = anyPeople.unpack(Viewer.class); System.out.println("Viewer:" + viewer + " "); } } } } }
现在,编译后,让我们首先执行writer −
> java -cp . arget\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output_silver Saved theater information with following data to disk: name: "SilverScreen" peopleInside { type_url: "type.googleapis.com/theater.Employee" value: " \004John" } peopleInside { type_url: "type.googleapis.com/theater.Viewer" value: " \004Jane\020\036" } peopleInside { type_url: "type.googleapis.com/theater.Employee" value: " \005Simon" } peopleInside { type_url: "type.googleapis.com/theater.Viewer" value: " \006Janice\020\031" }
注意 − 有两点需要注意 −
如果是 Any,Protobuf 会将任何标记内的内容打包/序列化为字节,然后将其存储为 'value'。基本上,这允许我们使用此 'Any' 标记发送任何消息类型。
我们还看到 "type.googleapis.com/theater.Viewer" 和 "type.googleapis.com/theater.Employee"。Protobuf 使用它来保存对象的类型以及数据,因为 Any 数据类型中的数据类型可能会有所不同。
现在让我们执行reader来读取同一个文件 −
java -cp . arget\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
因此,如我们所见,我们的 reader 代码能够成功区分 Employee 和 Viewer,即使它们位于同一个数组中。