- 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,即使它们出现在同一个数组中。