- Protocol Buffers 教程
- Protocol Buffers - 首页
- Protocol Buffers - 简介
- Protocol Buffers - 基本应用
- Protocol Buffers - 构造
- Protocol Buffers - 消息
- Protocol Buffers - 字符串
- Protocol Buffers - 数字
- Protocol Buffers - 布尔值
- Protocol Buffers - 枚举
- Protocol Buffers - 重复
- Protocol Buffers - 映射
- 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 - 快速指南
Protocol Buffers - 简介
在深入了解 Protocol Buffer 之前,让我们先简单了解一下序列化,因为 Protocol Buffer 就是用来做序列化的。
什么是序列化和反序列化?
序列化是指将一个对象(任何语言的对象)转换为字节,并将其存储在持久性内存系统中的过程。这个内存系统可以是磁盘上的文件、消息队列或数据库。序列化对象的主要目的是为了能够重用数据,并在同一台或不同的机器上重新创建该对象。在反序列化中,我们将存储的字节转换回对象。
为什么我们需要序列化和反序列化?
虽然还有其他一些用例,但最基本和最重要的用例是它提供了一种方法,可以通过网络将对象数据传输到不同的服务/机器等,然后重新创建对象以供进一步使用。通过 API、数据库或消息队列传输对象数据需要将对象转换为字节,以便可以通过网络发送。这就是序列化变得重要的原因。
在微服务架构中,应用程序被分解成小的服务,这些服务通过消息队列和 API 相互通信。所有这些通信都发生在网络上,这需要频繁地将对象转换为字节,然后再转换回对象。因此,在分布式环境中,序列化和反序列化变得非常关键。
为什么选择 Google Protocol Buffers?
Google Protocol Buffers 执行将对象序列化为字节的过程,这些字节可以传输到网络上。但也有其他一些库和机制可以传输数据。
那么,是什么让 Google Protocol Buffers 如此特别呢?以下是一些重要的特性:-
语言独立 - 多种语言都有 Protocol Buffers 库,其中一些著名的库包括 Java、Python、Go 等。因此,Java 对象可以由 Java 程序序列化为字节,并且可以反序列化为 Python 对象。
高效的数据压缩 - 在微服务环境中,考虑到网络上会发生大量的通信,因此我们发送的数据尽可能简洁至关重要。我们需要避免任何多余的信息,以确保数据能够快速传输。Google Protocol Buffers 将此作为其重点关注领域之一。
高效的序列化和反序列化 - 在微服务环境中,考虑到网络上会发生大量的通信,因此序列化和反序列化的速度至关重要。Google Protocol Buffers 确保序列化和反序列化数据的速度尽可能快。
易于使用 - Protocol Buffers 库会自动生成序列化代码(我们将在接下来的章节中看到),并具有版本控制方案,以确保数据创建者和数据使用者可以拥有序列化定义的不同版本等。
Protocol Buffers 与其他方案比较 (XML/JSON/Java 序列化)
让我们看看其他网络数据传输方式与 Protocol Buffers 相比如何。
| 特性 | Protocol Buffers | JSON | XML |
|---|---|---|---|
| 语言独立 | 是 | 是 | 是 |
| 序列化数据大小 | 三者中最小的 | 小于 XML | 三者中最大的 |
| 人类可读 | 否,因为它使用单独的编码方案 | 是,因为它使用基于文本的格式 | 是,因为它使用基于文本的格式 |
| 序列化速度 | 三者中最快的 | 比 XML 快 | 三者中最慢的 |
| 数据类型支持 | 比其他两者更丰富。支持复杂数据类型,如 Any、oneof 等。 | 支持基本数据类型 | 支持基本数据类型 |
| 支持演进模式 | 是 | 否 | 否 |
Protocol Buffers - 基本应用
概述
现在让我们使用 Google Protocol Buffer 并了解它在一个简单的问候应用中是如何工作的。在这个示例中,我们将创建一个简单的应用程序,该应用程序将执行以下操作:-
问候编写者 -
从用户处获取问候语和用户名
将上述信息存储在磁盘上的文件中
问候读取者 -
读取我们在上述文件中存储的同一文件
将该数据转换为对象并打印数据
Protocol Buffer 定义文件
协议缓冲区“定义文件”包含我们要序列化的数据的模式定义。数据存储在扩展名为".proto" 的人类可读文件中。
让我们将以下数据存储在greeting.proto 中,我们将在第一个应用程序中使用它。
syntax = "proto3";
package tutorialspoint;
option java_package = "com.tutorialspoint.greeting";
message Greet {
string greeting = 1;
string username = 2;
}
理解每个构造
现在,让我们仔细看看数据,并了解上面代码块中每一行代码的作用。
syntax = "proto3";
这里的syntax 表示我们使用的是哪个版本的 Protobuf。因此,我们使用的是最新的版本 3,因此模式可以使用对版本 3 有效的所有语法。
package tutorialspoint;
这里的package 用于冲突解决,例如,如果我们有多个具有相同名称的类/成员。
option java_package = "com.tutorialspoint.greeting";
此参数特定于 Java,即.proto 文件中代码将自动生成的包。
message Greet
将要创建/重新创建的对象的基本类的名称。
string greeting = 1; string username = 2;
这些是Greet 类的属性,以及数据类型和模式中标签的位置。如果要添加新的标签,它应该将“3”作为位置。请注意,此位置整数对于确保实际数据紧凑且有模式演进的余地非常重要。
Protocol Buffer 代码生成
现在我们已经定义了,让我们安装“proto”二进制文件,我们将使用它来自动生成上面Greet 类的代码。可以在"https://github.com/protocolbuffers/protobuf/releases/".找到二进制文件。
根据操作系统选择正确的二进制文件。我们将在 Windows 上安装 proto 二进制文件,但 Linux 的步骤差别不大。
我们已经下载了https://github.com/protocolbuffers/protobuf/releases/download/v27.3/protoc-27.3-win64.zip
验证 Proto 编译器设置
安装完成后,确保可以通过命令行访问它:-
protoc --version libprotoc 27.3
它确认 Protobuf 已正确安装。现在让我们继续为 Java创建上面描述的问候应用程序。
项目结构
以下是我们将拥有的整体项目结构:-
Java 中的问候应用程序
现在我们已经安装了protoc,我们可以使用protoc从 proto 文件自动生成代码。不过,让我们先创建一个 Java 项目。
以下是我们将用于 Java 项目的 Maven 配置。请注意,它还包含Protobuf-java所需的库。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tutorials.point</groupId>
<artifactId>protobuf-tutorial</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.27.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<!--Put your configurations here-->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
我们所有的代码都将位于src/main/java下。
在项目结构完成后,让我们为Greet类生成代码:-
生成 Java 类
protoc --java_out=. greeting.proto
执行命令后,您将在当前目录内的com > tutorialspoint > greeting文件夹下看到一个自动生成的类。
Greeting.java
此文件包含一个类Greeting和一个接口GreetOrBuilder,它们将帮助我们序列化和反序列化Greet对象。
使用生成的 Java 类
现在,让我们编写数据的写入器,它将以用户名和问候语作为输入:-
GreetWriter.java
package com.tutorialspoint.greeting;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
public class GreetWriter{
public static void main(String[] args) throws IOException {
Greet greeting = Greet.newBuilder()
.setGreeting(args[0])
.setUsername(args[1])
.build();
String filename = "greeting_protobuf_output";
System.out.println("Saving greeting to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
greeting.writeTo(output);
}
System.out.println("Saved greeting with following data to disk: \n" + greeting);
}
}
写入器只需获取 CLI 参数,创建Greet对象,将其序列化,然后将其转储到文件中。
现在让我们编写一个将读取文件的读取器:-
GreetReader.java
package com.tutorialspoint.greeting;
import java.io.FileInputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
public class GreetReader{
public static void main(String[] args) throws IOException {
Greet.Builder greetBuilder = Greet.newBuilder();
String filename = "greeting_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Greet greet = greetBuilder.mergeFrom(input).build();
System.out.println("Greeting: " + greet.getGreeting() + "\n" + "Username: " + greet.getUsername());
}
}
}
读取器只需从同一文件读取,将其反序列化,并打印有关问候语的数据。
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化 Java 对象
现在,让我们首先执行写入器以将对象序列化到文件系统中。
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.greeting.GreetWriter Hello John Saving greeting to file: greeting_protobuf_output Saved greeting with following data to disk: greeting: Hello username: John
反序列化已序列化的对象
然后,让我们执行读取器以从文件系统中反序列化对象。
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.greeting.GreetReader Reading from file greeting_protobuf_output Greeting: Hello Username: John
因此,正如我们看到的,写入器序列化并保存到文件中的数据,被读取器正确地反序列化并相应地打印出来。
Python 中的问候应用程序
现在让我们将同一个示例编写为 Python 项目:-
安装 protocol buffers 库
在继续之前,我们需要安装protobuf pip 包。
pip install protobuf
我们所有的代码都将位于python目录下。
从 proto 文件生成 Python 类
在项目结构完成后,让我们为 Greet 类生成代码:-
protoc --python_out=. greeting.proto
执行此命令后,您将在当前目录中看到一个自动生成的类greeting_pb2.py。此类将帮助我们序列化和反序列化Greet对象。
使用生成的 Python 类
现在,让我们编写数据的写入器,它将以用户名和问候语作为输入:-
greetWriter.py
import greeting_pb2
import sys
greet = greeting_pb2.Greet()
greet.username = sys.argv[1]
greet.greeting = sys.argv[2]
filename = "greeting_protobuf_output";
print("Saving to file: " + filename)
f = open(filename, "wb")
f.write(greet.SerializeToString())
f.close()
print("Saved following greeting to disk: \n" + str(greet))
写入器只需获取 CLI 参数,创建Greet对象,将其序列化,然后将其转储到文件中。
现在让我们创建一个将读取文件的读取器:-
greetReader.py
import greeting_pb2
greet = greeting_pb2.Greet()
filename = "greeting_protobuf_output";
print("Reading from file: " + filename)
f = open(filename, "rb")
greet.ParseFromString(f.read())
f.close()
print("Read greeting from disk: \n" + str(greet))
读取器只需从同一文件读取,将其反序列化,并打印有关问候语的数据。
序列化 Python 对象
现在,让我们首先执行写入器。
py greetWriter.py Hola Jane Saving to file: greeting_protobuf_output Saved following greeting to disk: greeting: "Hola" username: "Jane"
反序列化 Python 对象
然后,让我们执行读取器。
python greetReader.py Reading from file: greeting_protobuf_output Read greeting from disk: greeting: "Hola" username: "Jane"
因此,正如我们所看到的,写入器序列化并保存到文件中的数据。接下来,读取器正确地反序列化了相同的数据并相应地打印出来。
Protocol Buffers - 构造
概述
现在让我们来看一下 Google Protocol Buffers 提供的一些基本数据结构和数据类型。我们将使用电影院的例子来了解这些数据结构。
请注意,虽然我们将使用 Java 代码来演示这个结构,但在 Python 代码中使用它们也同样简单且可行。
在接下来的几章中,我们将逐一讨论以下 Protocol Buffers 数据类型:
数据类型
message − “message” 是 Protocol Buffers 的一个非常基本的基础构建块。它在我们使用的语言(例如 Java、Python 等)中转换为类。
string − “string” 数据类型在我们使用的语言(例如 Java、Python 等)中转换为字符串。
Numbers − 数字包括 Protocol Buffers 类型,如 int32、int64、float、double,它们是 Protobuf 的基本构建块。它分别在我们在使用的语言(例如 Java、Python 等)中转换为 int、long、float、double。
bool − “bool” 数据类型是 Protocol Buffers 的基本构建块之一。它在我们使用的语言(例如 Java、Python 等)中转换为布尔值。
enum − “enum” 是 Protocol Buffers 的复合数据类型之一。它在我们使用的语言(例如 Java)中转换为枚举。
repeated − “repeated” 用于创建数组或列表,是 Protocol Buffers 的复合数据类型之一。Protocol Buffers 将其转换为 Java 中的 java.util.list 接口。
map − “Map” 是 Protocol Buffers 的复合数据类型之一。Protocol Buffers 将其转换为 Java 中的 java.util.Map 接口。
嵌套类 − 我们可以使用用“message”创建的类在另一个“message”中,从而创建嵌套类。Protocol Buffers 将其转换为嵌套的 Java 类。
Protocol Buffers - 消息
概述
Protocol Buffers 的最基本构建块是message属性。它在我们使用的语言(例如 Java、Python 等)中转换为类。
代码示例
以下是我们需要使用的语法,用于指示 Protocol Buffers 我们将创建给定类的实例:
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
}
我们将以上内容保存在“theater.proto”中,并在我们探索其他数据结构时使用它。
解释
这里的“syntax”表示我们使用的是哪个版本的 Protocol Buffers。因此,我们使用的是最新的版本 3,并且该模式可以使用所有对版本 3 有效的语法。
syntax = "proto3";
这里的包用于冲突解决,例如,如果我们有多个同名的类/消息。
package tutorial;
此参数特定于 Java,即“.proto”文件中的代码将自动生成的包。
option java_package = "com.tutorialspoint.greeting";
现在我们完成了先决条件,这里最后一项是:
message Theater
这仅仅是将要创建/重新创建的对象的基础类的类名。请注意,它在当前状态下毫无用处,因为它没有任何其他属性。但随着我们继续,我们将添加更多属性。
使用多个 message 属性
单个 proto 文件也可以有多个类/消息。例如,如果我们想,我们也可以在同一个文件中添加Visitors消息/类。Protocol Buffers 将确保为其创建两个独立的类。例如:
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
}
message Visitor {
}
从 proto 文件创建 Java 类
要使用 Protocol Buffers,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. proto_files\theater.proto
使用从 proto 文件创建的 Java 类
就是这样!以上命令应该在当前目录中创建所需的文件,现在我们可以在我们的 Java 代码中使用它们了:
Theater theater = Theater.newBuilder().build() Visitor visitor = Visitor.newBuilder().build()
在这个阶段,它没有太大用处,因为我们还没有向成员/类添加任何属性。当我们在Protocol Buffers - string章节中查看字符串时,我们将执行此操作。
Protocol Buffers - 字符串
概述
Protobuf 字符串在我们在使用的语言(例如 Java、Python 等)中转换为字符串。继续使用theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建一个字符串:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
现在我们的类/消息包含两个字符串属性。它们每个都具有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要具有唯一的位置属性。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 Proto 文件创建的 Java 类
首先让我们创建一个writer来写入theater信息:
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()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
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 对象
现在,编译后,让我们先执行writer:
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: "Silver Screener" address: "212, Maple Street, LA, California"
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的字符串。在下一章Protocol Buffers - Numbers中,我们将了解数字。
Protocol Buffers - 数字
概述
数字包括 protobuf 类型,如int32、int64、float、double,它们是 Protobuf 的基本构建块。它分别在我们在使用的语言(例如 Java、Python 等)中转换为int、long、float、double。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建数字:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
int32 total_capcity = 3;
int64 mobile = 4;
float base_ticket_price = 5;
}
现在我们的类/消息包含数值属性。它们每个都具有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要分配一个唯一的数字。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 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()
.setTotalCapcity(320)
.setMobile(98234567189L)
.setBaseTicketPrice(22.45f)
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
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.getBaseTicketPrice());
System.out.println(theater);
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化 Java 对象
现在,编译后,让我们先执行writer:
> 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: total_capcity: 320 mobile: 98234567189 base_ticket_price: 22.45
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output 22.45 total_capcity: 320 mobile: 98234567189 base_ticket_price: 22.45
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的int、float和long。在下一章Protocol Buffers - bool中,我们将了解布尔类型。
Protocol Buffers - 布尔值
概述
bool数据类型是 Protobuf 的基本构建块之一。它在我们使用的语言(例如Java、Python等)中转换为布尔值。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建bool:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
bool drive_in = 6;
}
现在我们的消息类包含一个bool属性。它还有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要分配一个唯一的数字。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 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()
.setDriveIn(true)
.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);
}
}
接下来,我们将有一个reader来读取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.getDriveIn());
System.out.println(theater);
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化 Java 对象
现在,编译后,让我们先执行writer:
> 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: drive_in: true
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output drive_in: true
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的bool。在下一章Protocol Buffers - enum中,我们将了解枚举,一种复合类型。
Protocol Buffers - 枚举
概述
enum数据类型是 Protobuf 的复合数据类型之一。它在我们使用的语言(例如Java等)中转换为枚举。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建一个enum:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
enum PAYMENT_SYSTEM{
CASH = 0;
CREDIT_CARD = 1;
DEBIT_CARD = 2;
APP = 3;
}
PAYMENT_SYSTEM payment = 7;
}
现在我们的消息类包含一个enum属性。它还有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要分配一个唯一的数字。
我们定义了enum并在下面将其与“payment”属性一起用作数据类型。请注意,尽管我们在消息类内部定义了枚举,但它也可以驻留在其外部。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 Proto 文件创建的 Java 类
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
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.getPayment());
System.out.println(theater);
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化 Java 对象
现在,编译后,让我们先执行writer:
> 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: payment: CREDIT_CARD
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output CREDIT_CARD payment: CREDIT_CARD
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的enum。在下一章Protocol Buffers - repeated中,我们将了解repeated,一种复合类型。
Protocol Buffers - 重复
概述
repeated数据类型是 Protobuf 的复合数据类型之一。它在Java中转换为java.util.List接口。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建一个repeated:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
repeated string snacks = 8;
}
现在我们的消息类包含一个repeated属性。请注意,尽管我们有一个字符串repeated,但我们也可以有数字、布尔值、自定义数据类型列表。它还有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要分配一个唯一的数字。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 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.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
List<String> snacks = new ArrayList<>();
snacks.add("Popcorn");
snacks.add("Coke");
snacks.add("Chips");
snacks.add("Soda");
Theater theater = Theater.newBuilder()
.addAllSnacks(snacks)
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
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 对象
现在,编译后,让我们先执行writer:
> 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: snacks: "Popcorn" snacks: "Coke" snacks: "Chips" snacks: "Soda"
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output snacks: "Popcorn" snacks: "Coke" snacks: "Chips" snacks: "Soda"
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的repeated。在下一章Protocol Buffers - map中,我们将了解map,一种复合类型。
Protocol Buffers - 映射
概述
map数据类型是 Protobuf 的复合数据类型之一。它在Java中转换为java.util.Map接口。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建一个repeated:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
map<string, int32> movieTicketPrice = 9;
}
现在我们的消息类包含一个电影及其票价的map。请注意,尽管我们有“string -> int”map,但我们也可以有数字、布尔值和自定义数据类型。但是,请注意,我们不能有嵌套的map。它还有一个位置,这是 Protobuf 在序列化和反序列化时使用的。成员的每个属性都需要分配一个唯一的数字。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 Proto 文件创建的 Java 类
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Map<String, Integer> ticketPrice = new HashMap<>();
ticketPrice.put("Avengers Endgame", 700);
ticketPrice.put("Captain America", 200);
ticketPrice.put("Wonder Woman 1984", 400);
Theater theater = Theater.newBuilder()
.putAllMovieTicketPrice(ticketPrice)
.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);
}
}
接下来,我们将有一个reader来读取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 对象
现在,编译后,让我们先执行writer:
> 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:
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader
Reading from file theater_protobuf_output
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的map。在下一章Protocol Buffers - Nested Class中,我们将了解嵌套类。
Protocol Buffers - 嵌套类
概述
在这里,我们将看到如何创建嵌套类。Protobuf 将其转换为嵌套的Java类。
继续我们来自Protocol Buffers - String章节的theater示例,以下是我们需要使用的语法,用于指示 Protobuf 我们将创建一个repeated:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
TheaterOwner owner = 10;
}
message TheaterOwner{
string name = 1;
string address = 2;
}
现在我们的消息类包含一个嵌套类,即影院所有者信息。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 Proto 文件创建的 Java 类
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.TheaterOwner;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
TheaterOwner owner = TheaterOwner.newBuilder()
.setName("Anthony Gonsalves")
.setAddress("513, St Paul Street, West Coast, California")
.build();
Theater theater = Theater.newBuilder()
.setOwner(owner)
.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);
}
}
接下来,我们将有一个reader来读取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 对象
现在,编译后,让我们先执行writer:
> 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:
owner {
name: "Anthony Gonsalves"
address: "513, St Paul Street, West Coast, California"
}
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader
Reading from file theater_protobuf_output
owner {
name: "Anthony Gonsalves"
address: "513, St Paul Street, West Coast, California"
}
因此,正如我们所看到的,我们能够通过将二进制数据反序列化到Theater对象来读取序列化的嵌套类。
Protocol Buffers - 可选性和默认值
概述
虽然我们已经了解了各种数据类型以及如何使用它们,但如果在序列化时不指定值会发生什么?“proto2”版本支持“required”和“optional”标签,这有助于确定如果所需的解析逻辑不可用,序列化/反序列化是否应该失败。但“required”标签在“proto3”版本中被移除。失败部分需要由相应的代码处理。现在每个属性都是可选的,并且都有默认值。因此,从“proto3”版本开始,“optional”的使用就变得多余了。
Protocol Buffers根据下表支持其数据类型的默认值:
| 数据类型 | 默认值 |
|---|---|
| Int32 / Int64 | 0 |
| Float/double | 0.0 |
| String | 空字符串 |
| Boolean | False |
| Enum | 第一个枚举项,即“index=0”的项 |
| 重复类型 | 空列表 |
| Map | 空Map |
| 嵌套类 | null |
因此,如果未为这些数据类型指定数据,则它们将采用上述默认值。现在,让我们继续使用我们的theater示例来演示其工作原理。
在此示例中,我们将让所有字段都使用默认值。唯一指定的字段将是剧院的名称。
继续我们从Protocol Buffers - String 章节中的theater示例,以下是我们需要使用的语法来指示Protobuf我们将创建不同的数据类型:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
int32 total_capcity = 3;
int64 mobile = 4;
float base_ticket_price = 5;
bool drive_in = 6;
enum PAYMENT_SYSTEM {
CASH = 0;
CREDIT_CARD = 1;
DEBIT_CARD = 2;
APP = 3;
}
PAYMENT_SYSTEM payment = 7;
repeated string snacks = 8;
map<string, int32> movieTicketPrice = 9;
TheaterOwner owner = 10;
}
message TheaterOwner{
string name = 1;
string address = 2;
}
现在我们的message类包含多个属性。
从 Proto 文件创建 Java 类
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 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()
.setName("SilverScreen")
.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);
}
}
接下来,我们将有一个reader来读取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(
"Name:" + theater.getName() + "\n" +
"Address:" + theater.getAddress() + "\n" +
"Drive_In:" + theater.getDriveIn() + "\n" +
"Total Capacity:" + theater.getTotalCapcity() + "\n" +
"Base Ticket Prices: " + theater.getBaseTicketPrice() + "\n" +
"Owner: " + theater.getOwner() + "\n" +
"Snacks: " + theater.getSnacksList() + "\n" +
"Payment: " + theater.getPayment()
);
//Map<FieldDescriptor, Object> f = theater.getAllFields();
System.out.println("List of fields explicitly specified: " + theater.getAllFields());
}
}
}
编译项目
现在我们已经设置了读取器和写入器,让我们编译项目。
mvn clean install
序列化 Java 对象
现在,编译后,让我们先执行writer:
> 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"
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader
Reading from file theater_protobuf_output
Name:SilverScreen
Address:
Drive_In:false
Total Capacity:0
Base Ticket Prices: 0.0
Owner:
Snacks: []
Payment: CASH
List of fields explicitly specified: {theater.Theater.name=SilverScreen}
因此,正如我们所看到的,除了我们已明确指定为最底层行的name之外,所有值都相应地使用了默认值。
Protocol Buffers - 语言独立性
概述
到目前为止,我们一直在使用Java来序列化和反序列化电影院数据。但是,Google Protocol Buffers提供的关键功能之一是“语言独立性”。在本节中,我们将了解如何使用Java进行序列化,并使用Python进行反序列化。
继续我们从Protocol Buffers - String 章节中的theater示例,以下是我们需要使用的语法来指示Protobuf我们将创建不同的数据类型:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
int32 total_capcity = 3;
int64 mobile = 4;
float base_ticket_price = 5;
bool drive_in = 6;
enum PAYMENT_SYSTEM {
CASH = 0;
CREDIT_CARD = 1;
DEBIT_CARD = 2;
APP = 3;
}
PAYMENT_SYSTEM payment = 7;
repeated string snacks = 8;
map<string, int32> movieTicketPrice = 9;
TheaterOwner owner = 10;
}
message TheaterOwner{
string name = 1;
string address = 2;
}
使用Java进行序列化
要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
这将在当前目录中的com > tutorialspoint > theater文件夹中创建一个 TheaterOuterClass.java 类。我们将在我们的应用程序中使用此类,类似于在Protocol Buffers - Basic App章节中所做的那样。
使用从 Proto 文件创建的 Java 类
首先,我们将创建一个写入器来写入剧院信息:
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.TheaterOwner;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;
public class TheaterWriter {
public static void main(String[] args) throws IOException {
TheaterOwner owner = TheaterOwner.newBuilder()
.setName("Anthony Gonsalves")
.setAddress("513, St Paul Street, West Coast, California")
.build();
List<String> snacks = new ArrayList<>();
snacks.add("Popcorn");
snacks.add("Coke");
snacks.add("Chips");
snacks.add("Soda");
Map<String, Integer> ticketPrice = new HashMap<>();
ticketPrice.put("Avengers Endgame", 700);
ticketPrice.put("Captain America", 200);
ticketPrice.put("Wonder Woman 1984", 400);
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.setDriveIn(true)
.setTotalCapcity(320)
.setMobile(98234567189L)
.setBaseTicketPrice(22.45f)
.setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
.putAllMovieTicketPrice(ticketPrice)
.addAllSnacks(snacks)
.setOwner(owner)
.build();
String filename = "E:/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);
}
}
让我们编译项目。
mvn clean install
序列化 Java 对象
现在,编译后,让我们执行writer:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter
Saving theater information to file: E:/theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
owner {
name: "Anthony Gonsalves"
address: "513, St Paul Street, West Coast, California"
}
使用Python反序列化序列化对象
从 proto 文件生成 Python 类
让我们为Theater类生成Python代码:
protoc --python_out=. theater.proto
执行此命令后,您将在当前目录中注意到一个自动生成的类theater_pb2.py。此类将帮助我们反序列化Theater对象。
使用生成的 Python 类
现在,让我们编写数据的读取器,它将使用Java读取包含序列化对象的文件:
theaterReader.py
import theater_pb2
filename = "E:/theater_protobuf_output";
print("Reading from file: " + filename)
theater = theater_pb2.Theater()
f = open(filename, "rb")
theater.ParseFromString(f.read())
f.close()
print("Read theater from disk: \n" + str(theater))
然后,让我们执行读取器。
python theaterReader.py
Reading from file: E:/greeting_protobuf_output
Read theater from disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
owner {
name: "Anthony Gonsalves"
address: "513, St Paul Street, West Coast, California"
}
因此,正如我们所看到的,Java客户端写入的所有值都被正确地反序列化并由我们的Python客户端读取,这有效地意味着Protobuf是语言独立的。
Protocol Buffers - 复合数据类型
概述
还有两种复合数据类型可能对复杂的用例有用。它们是“OneOf”和“Any”。在本节中,我们将了解如何使用Protobuf的这两种数据类型。
OneOf
我们将一些参数传递给此OneOf数据类型,Protobuf确保其中只有一个被设置。如果我们设置了其中一个并尝试设置另一个,则第一个属性将被重置。让我们通过一个示例来了解这一点。
继续我们从Protocol Buffers - String 章节中的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 - Basic App章节中所做的那样。
使用从 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);
}
}
接下来,我们将有一个reader来读取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 对象
现在,编译后,让我们先执行writer:
> 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"
反序列化已序列化的对象
现在,让我们执行reader从同一个文件读取:
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不会报错。让我们通过一个示例来了解这一点。
继续剧院示例,假设我们想要跟踪剧院内部的人员。其中一些可能是员工,另一些可能是观众。但最终它们都是人,因此我们将把它们放在一个包含这两种类型的列表中。
现在我们的class/message包含一个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 - Basic App章节中所做的那样。
使用从 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);
}
}
接下来,我们将有一个reader来读取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 对象
现在,编译后,让我们先执行writer:
> 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数据类型中的数据类型可能会有所不同。
反序列化已序列化的对象
现在让我们执行reader以从同一文件读取:
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
因此,正如我们所看到的,即使我们的reader代码位于同一个数组中,也能够成功地区分Employee和Viewer。
Protocol Buffers - 命令行使用
概述
Protobuf序列化数据并将其存储在二进制格式中。如果我们只是处理字符串,这可能不是问题,因为最终Protobuf使用UTF-8。因此,如果使用支持UTF8的读取器,它存储的任何文本都将是人类可读的。但是,int32、Boolean、list、maps等使用特定的技术进行编码以减少空间消耗。
这就是为什么有时通过简单的命令行实用程序编码/解码消息对于测试目的很有用。让我们看看它是如何工作的:
假设我们使用以下简单的"greeting_cli.proto":
syntax = "proto3";
package tutorial;
option java_package = "com.tutorialspoint.greeting";
message Greet {
string greeting = 1;
string username = 2;
int32 age = 3;
}
并且我们在cli_greeting_message中创建一条消息:
greeting: "Yo" username : "John" age : 50
现在,让我们使用Protobuf CLI工具对该消息进行编码:
cat .\cli_greeting_msg.proto | protoc --encode=tutorial.Greet .\greeting_cli.proto > encoded_greeting
如果我们查看此文件内部的内容或cat此文件:
cat .\encoded_greeting ☻Yo↕♦John↑2
除了"Yo"和"John"之外,您还会注意到一些奇怪的字符。这是因为这些编码可能不是有效的Unicode/UTF-8编码。UTF-8通常是在大多数地方使用的。在Protobuf的情况下,它用于string,但ints、maps、Boolean、list有单独的格式。此外,此文件还包含数据的元数据。
这就是为什么我们需要解码器/反序列化器来读取这些数据。让我们使用它。
cat .\encoded_greeting | protoc --decode=tutorial.Greet .\greeting_cli.proto greeting: "Yo" username : "John" age : 50
因此,正如我们所看到的,我们能够获取序列化后并在文件中看起来很奇怪的数据。
Protocol Buffers - 更新定义规则
概述
假设您已经提出了将在生产环境中使用的proto文件的定义。将来肯定会有需要更改此定义的时候。在这种情况下,必须使我们进行的更改符合某些规则,以便更改向后兼容。让我们通过一些注意事项来了解这一点。
在writer中添加一个新字段,而reader保留旧版本的代码。
假设您决定添加一个新字段。理想情况下,要添加新字段,我们将不得不同时更新writer和reader。但是,在大规模部署中,这是不可能的。在某些情况下,writer已更新,但reader尚未更新新字段。这就是上述情况发生的地方。让我们看看它是如何工作的。
继续我们的theater示例,假设我们的proto文件中只有一个标签,即'name'。以下是我们需要使用的语法来指示Protobuf:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
}
要使用Protobuf,我们现在将不得不使用protoc二进制文件从这个".proto"文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=. theater.proto
上述命令应该创建所需的文件,现在我们可以在Java代码中使用它。首先,我们将创建一个writer来写入theater信息:
TheaterWriter.java
package com.tutorialspoint.theater;
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()
.setName("Silver Screener")
.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);
}
}
接下来,我们将有一个reader来读取剧院信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
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);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
现在,编译后,让我们先执行writer:
> 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: "Silver Screener"
现在让我们执行reader以从同一文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener"
未知字段
我们根据Protobuf定义简单地写入了一个字符串,并且reader能够读取该字符串。我们还看到读取器没有意识到任何未知字段。
但是现在,假设我们想在Protobuf定义中添加一个新的字符串'address'。现在,它将如下所示:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
我们还将更新我们的writer并添加一个address字段:
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.build();
在编译之前,将上一次编译生成的JAR重命名为protobuf-tutorial-old-1.0.jar。然后编译。
现在,编译后,让我们先执行writer:
> 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: "Silver Screener" address: "212, Maple Street, LA, California"
现在让我们执行reader以从同一文件但从旧JAR中读取:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output Reading from file theater_protobuf_output name: "Silver Screener" 2: "212, Maple Street, LA, California" Unknown fields: 2: "212, Maple Street, LA, California"
从输出的最后一行可以看出,旧的reader不知道新writer添加的address字段。它只是展示了“新writer - 旧reader”组合的功能。
删除字段
假设您决定删除现有字段。理想情况下,要使已删除字段立即生效,我们将不得不同时更新writer和reader。但是,在大规模部署中,这是不可能的。在某些情况下,writer已更新,但reader尚未更新。在这种情况下,reader仍将尝试读取已删除的字段。让我们看看它是如何工作的。
继续我们的theater示例,假设我们的proto文件中只有两个标签。以下是我们需要使用的语法来指示Protobuf:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
要使用Protobuf,我们现在将不得不使用protoc二进制文件从这个".proto"文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令应该创建所需的文件,现在我们可以在Java代码中使用它。首先,我们将创建一个writer来写入theater信息:
TheaterWriter
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()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
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);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
现在,编译后,让我们首先执行writer:
> 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: "Silver Screener" address: "212, Maple Street, LA, California"
现在让我们执行reader以从同一文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
所以,这里没有什么新东西,我们只是根据Protobuf定义简单地写了一个string,并且reader能够读取string。
但是现在,假设我们想从Protobuf定义中删除字符串'address'。因此,定义将如下所示:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
}
我们还将更新我们的writer,如下所示:
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.build();
在编译之前,将上一次编译生成的JAR重命名为protobuf-tutorial-old-1.0.jar。然后编译。
现在,编译后,让我们先执行writer:
> 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: "Silver Screener"
现在让我们执行reader以从同一文件但从旧JAR中读取:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output Reading from file theater_protobuf_output name: "Silver Screener" address:
从输出的最后一行可以看出,旧的reader默认为“address”的值。它展示了“新writer - 旧reader”组合的功能。
避免重复使用字段的序列号
在某些情况下,我们可能会错误地更新字段的“序列号”。这可能存在问题,因为序列号对于Protobuf理解和反序列化数据至关重要。一些旧的reader可能依赖此序列号来反序列化数据。因此,建议您:
不要更改字段的序列号
不要重复使用已删除字段的序列号。
让我们通过交换字段标签来了解这一点。
继续使用剧院示例,假设我们只有两个标签在我们的proto文件中。以下是我们需要用来指示 Protobuf 的语法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
要使用Protobuf,我们现在将不得不使用protoc二进制文件从这个".proto"文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令应该创建所需的文件,现在我们可以在Java代码中使用它。首先,我们将创建一个writer来写入theater信息:
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()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.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);
}
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
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);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
现在,编译后,让我们先执行writer:
> 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: "Silver Screener" address: "212, Maple Street, LA, California"
接下来,让我们执行读取器从同一个文件读取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
这里,我们只是根据我们的 Protobuf 定义写了简单的字符串,并且读取器能够读取字符串。但是现在,让我们在 Protobuf 定义中交换序列号,使其如下所示:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 2;
string address = 1;
}
在编译之前,将 JAR 从之前的编译重命名为protobuf-tutorial-old-1.0.jar。然后编译。
现在,编译后,让我们首先执行writer:
> 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: address: "212, Maple Street, LA, California" name: "Silver Screener"
现在让我们执行reader以从同一文件但从旧JAR中读取:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "212, Maple Street, LA, California" address: "Silver Screener"
从输出中可以看到,旧的读取器交换了地址和名称。这表明更新序列号以及结合“新的写入器-旧的读取器”不会按预期工作。
更重要的是,这里我们有两个字符串,这就是我们能够看到数据的原因。如果我们使用了不同的数据类型,例如int32、布尔值、映射等,Protobuf 会放弃并将此视为未知字段。
因此,务必不要更改字段的序列号或重复使用已删除字段的序列号。
更改字段类型
在某些情况下,我们需要更新属性/字段的类型。Protobuf 对此有一些兼容性规则。并非所有类型都可以转换为其他类型。需要注意的一些基本类型:
如果字节是 UTF-8,则字符串和字节是兼容的。这是因为 Protobuf 始终将字符串编码/解码为 UTF-8。
就值而言,枚举与int32和int64兼容,但是客户端可能无法按预期反序列化它。
int32、int64(无符号也)以及布尔值是兼容的,因此可以互换。类似于语言中的强制转换工作方式,可能会截断多余的字符。
但是更改类型时需要非常小心。让我们通过将int64转换为int32的错误示例来查看其操作。
继续使用剧院示例,假设我们只有两个标签在我们的proto文件中。以下是我们需要用来指示 Protobuf 的语法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
int64 total_capacity = 2;
}
要使用Protobuf,我们现在将不得不使用protoc二进制文件从这个".proto"文件创建所需的类。让我们看看如何做到这一点:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令应该创建所需的文件,现在我们可以在Java代码中使用它。首先,我们将创建一个writer来写入theater信息:
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()
.setName("Silver Screener")
.setTotalCapacity(2300000000L)
.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);
}
}
现在,编译后,让我们先执行writer:
> 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: "Silver Screener" total_capacity: 2300000000
假设我们对读取器使用不同版本的proto文件:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
int64 total_capacity = 2;
}
接下来,我们将有一个reader来读取theater信息:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
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);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
现在让我们执行reader以从同一文件读取:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
所以,这里没有什么新内容,我们只是根据 Protobuf 定义写了简单的字符串,并且读取器能够读取字符串。但是现在,让我们在 Protobuf 定义中交换序列号,使其如下所示:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 2;
int32 total_capacity = 2;
}
在编译之前,将 JAR 从之前的编译重命名为protobuf-tutorial-old-1.0.jar。然后编译。
现在,编译后,让我们先执行writer:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Reading from file theater_protobuf_output address: "Silver Screener" total_capcity: -1994967296
从输出中可以看到,旧的读取器将数字从int64转换,但是给定的int32没有足够的空间来容纳数据,它环绕到负数。此环绕是 Java 特定的,与 Protobuf 无关。
因此,我们需要从int32升级到int64,而不是反过来。如果我们仍然希望从int64转换为int32,我们需要确保值实际上可以保存在 31 位中(1 位用于符号位)。
Protocol Buffers - Kafka 集成
我们已经介绍了很多关于 Protocol Buffers 及其数据类型的示例。在本章中,让我们再举一个例子,看看 Protocol Buffers 如何与 Kafka 使用的 Schema Registry 集成。让我们首先了解什么是“schema registry”。
Schema Registry
Kafka 是最广泛使用的消息队列之一。它用于大规模应用发布者-订阅者模型。有关 Kafka 的更多信息,请访问此处:https://tutorialspoint.com/apache_kafka/index.htm
但是,在基本层面上,Kafka生产者应该发送消息,即 Kafka消费者可以读取的信息片段。而这种消息的发送和消费是我们需要 schema 的地方。它在大型组织中尤其需要,在大型组织中,多个团队读取/写入 Kafka 主题。Kafka 提供了一种将此 schema 存储在schema registry中的方法,这些 schema 随后在生产者/消费者创建/消费消息时创建/使用。
维护 schema 有两个主要好处:
兼容性:在大型组织中,必须确保生成消息的团队不会破坏使用这些消息的下游工具。Schema registry 确保更改向后兼容。
高效编码:在每条消息中发送字段名称及其类型效率低下且计算量大。使用 schema,我们不需要在每条消息中发送此信息。
schema registry 支持Avro、Google Protocol Buffers和JSON Schema 作为 schema 语言。这些语言中的 schema 可以存储在 schema registry 中。在本教程中,我们需要 Kafka 设置和 Schema registry 设置。
有关 Kafka 的安装,您可以查看以下链接:
安装 Kafka 后,您可以通过更新/etc/schema-registry/schema-registry.properties文件来设置 Schema Registry。
# where should schema registry listen on listeners=http://0.0.0.0:8081 # Schema registry uses Kafka beneath it, so we need to tell where are the Kafka brokers available kafkastore.bootstrap.servers=PLAINTEXT://hostname:9092,SSL://hostname2:9092 Once done, you can then run: sudo systemctl start confluent-schema-registry
设置完成后,让我们开始将 Google Protocol Buffers 与 Schema Registry 一起使用。
使用 Protocol Buffers Schema 的 Kafka 生产者
让我们继续使用我们的剧院示例。我们将使用以下 Protocol Buffers schema:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
int32 total_capcity = 3;
int64 mobile = 4;
float base_ticket_price = 5;
bool drive_in = 6;
enum PAYMENT_SYSTEM{
CASH = 0;
CREDIT_CARD = 1;
DEBIT_CARD = 2;
APP = 3;
}
PAYMENT_SYSTEM payment = 7;
repeated string snacks = 8;
map<string, int32> movieTicketPrice = 9;
}
现在,让我们创建一个简单的 Kafka写入器,它会将以这种格式编码的消息写入 Kafka 主题。但要做到这一点,首先我们需要向我们的 Maven POM 添加一些依赖项:
Kafka 客户端用于 Kafka 生产者和消费者
Kafka Protocol Buffers 序列化程序用于序列化和反序列化消息
Slf4j simple 确保我们从 Kafka 获取日志
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>2.5.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.confluent/kafka-protobuf-serializer --> <dependency> <groupId>io.confluent</groupId> <artifactId>kafka-protobuf-serializer</artifactId> <version>5.5.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.30</version> </dependency>
完成此操作后,让我们现在创建一个 Kafka生产者。此生产者将创建并发送一条消息,其中包含剧院对象。
KafkaProtbufProducer.java
package com.tutorialspoint.kafka;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;
public class KafkaProtbufProducer {
public static void main(String[] args) throws Exception{
String topicName = "testy1";
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("clientid", "foo");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "io.confluent.kafka.serializers.protobuf.KafkaProtocol BuffersSerializer");
props.put("schema.registry.url", "https://:8081");
props.put("auto.register.schemas", "true");
Producer<String, Theater> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<String, Theater>(topicName, "SilverScreen", getTheater())).get();
System.out.println("Sent to Kafka: \n" + getTheater());
producer.flush();
producer.close();
}
public static Theater getTheater() {
List<String> snacks = new ArrayList<>();
snacks.add("Popcorn");
snacks.add("Coke");
snacks.add("Chips");
snacks.add("Soda");
Map<String, Integer> ticketPrice = new HashMap<>();
ticketPrice.put("Avengers Endgame", 700);
ticketPrice.put("Captain America", 200);
ticketPrice.put("Wonder Woman 1984", 400);
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.setDriveIn(true)
.setTotalCapacity(320)
.setMobile(98234567189L)
.setBaseTicketPrice(22.45f)
.setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
.putAllMovieTicketPrice(ticketPrice)
.addAllSnacks(snacks)
.build();
return theater;
}
}
以下是我们需要了解的一些要点:
我们需要将 Schema Registry URL 传递给生产者。
我们还需要传递正确的 Protocol Buffers 序列化程序,该序列化程序特定于 Schema Registry。
当我们完成发送后,schema registry 会自动存储剧院对象的 schema。
最后,我们从自动生成的 Java 代码创建了一个剧院对象,这就是我们要发送的内容。
输出
现在,让我们编译并执行代码:
mvn clean install ; java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.kafka.KafkaProtbufProducer
我们将看到以下输出:
[main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka version: 2.5.0 [main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka commitId: 66563e712b0b9f84 [main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka startTimeMs: 1621692205607 [kafka-producer-network-thread | producer-1] INFO org.apache.kafka.clients.Metadata - [Producer clientId=producer-1] Cluster ID: 7kwQVXjYSz--bE47MiXmjw
发送到 Kafka
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capacity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
[main] INFO org.apache.kafka.clients.producer.KafkaProducer -
[Producer clientId=producer-1] Closing the Kafka producer with timeoutMillis = 9223372036854775807 ms.
这意味着我们的消息已发送。
现在,让我们确认 schema 已存储在 Schema Registry 中。
curl -X GET https://:8081/subjects | jq
显示的输出是“topicName” + “key/value”
[ "testy1-value" ]
我们还可以看到 registry 存储的 schema:
curl -X GET https://:8081/schemas/ids/1 | jq {
"schemaType": "PROTOBUF",
"schema": "syntax = \"proto3\";\npackage theater;\n\noption java_package = \"com.tutorialspoint.theater\";\n\nmessage Theater {
\n string name = 1;\n string address = 2;\n int64 total_capacity = 3;\n
int64 mobile = 4;\n float base_ticket_price = 5;\n bool drive_in = 6;\n
.theater.Theater.PAYMENT_SYSTEM payment = 7;\n repeated string snacks = 8;\n
repeated .theater.Theater.MovieTicketPriceEntry movieTicketPrice = 9;\n\n
message MovieTicketPriceEntry {\n option map_entry = true;\n \n
string key = 1;\n int32 value = 2;\n }\n enum PAYMENT_SYSTEM {
\n CASH = 0;\n CREDIT_CARD = 1;\n DEBIT_CARD = 2;\n APP = 3;\n
}\n
}\n"
}
使用 Protocol Buffers Schema 的 Kafka 消费者
现在让我们创建一个 Kafka消费者。此消费者将使用包含剧院对象的消息。
KafkaProtbufConsumer.java
package com.tutorialspoint.kafka;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;
public class KafkaProtbufConsumer {
public static void main(String[] args) throws Exception{
String topicName = "testy1";
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("clientid", "foo");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "io.confluent.kafka.serializers.protobuf.KafkaProtocol BuffersSerializer");
props.put("schema.registry.url", "https://:8081");
props.put("auto.register.schemas", "true");
Producer<String, Theater> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<String, Theater>(topicName, "SilverScreen", getTheater())).get();
System.out.println("Sent to Kafka: \n" + getTheater());
producer.flush();
producer.close();
}
public static Theater getTheater() {
List<String> snacks = new ArrayList<>();
snacks.add("Popcorn");
snacks.add("Coke");
snacks.add("Chips");
snacks.add("Soda");
Map<String, Integer> ticketPrice = new HashMap<>();
ticketPrice.put("Avengers Endgame", 700);
ticketPrice.put("Captain America", 200);
ticketPrice.put("Wonder Woman 1984", 400);
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.setDriveIn(true)
.setTotalCapacity(320)
.setMobile(98234567189L)
.setBaseTicketPrice(22.45f)
.setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
.putAllMovieTicketPrice(ticketPrice)
.addAllSnacks(snacks)
.build();
return theater;
}
}
以下是我们需要了解的一些要点:
我们需要将 Schema Registry URL 传递给消费者。
我们还需要传递正确的 Protocol Buffers 反序列化程序,该反序列化程序特定于 Schema Registry。
当我们完成消费后,Schema Registry 会自动读取存储的剧院对象的 schema。
最后,我们从自动生成的 Java 代码创建了一个剧院对象,这就是我们要发送的内容。
输出
现在,让我们编译并执行代码:
mvn clean install ; java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.kafka.KafkaProtbufConsumer
offset = 0, key = SilverScreen, value = May 22, 2021 7:50:15 PM com.google.protobuf.TextFormat$Printer$MapEntryAdapter compareTo
May 22, 2021 7:50:15 PM com.google.protobuf.TextFormat$Printer$MapEntryAdapter compareTo
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capacity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
key: "Captain America"
value: 200
}
movieTicketPrice {
key: "Wonder Woman 1984"
value: 400
}
movieTicketPrice {
key: "Avengers Endgame"
value: 700
}
因此,正如我们所看到的,写入 Kafka 的消息被消费者正确地使用了。此外,Registry 存储了可以通过 REST API 访问的 schema。
Protocol Buffers - 其他语言
我们一直在 Java 和 Python 中使用 Protocol Buffers。但它支持多种语言,包括 C++、C#、Kotlin、Dart、Go 等。基本内容大多保持不变,即编写proto schema,通过protoc二进制文件生成源代码,我们的代码可以使用该源代码。让我们在本节中为Go和Dart编写一个基本示例。
我们将使用以下proto文件:
greeting.proto
syntax = "proto3";
package tutorial;
message Greet {
string greeting = 1;
string username = 2;
}
在 Go Lang 中使用 Google Protocol Buffers
要使用上述 Protocol Buffers 文件,我们首先必须为Go语言中的Greet类生成代码。为此,我们需要执行以下操作:
安装Go Protocol Buffers 插件 (protoc-gen-go),这是我们一直在使用的protoc文件的先决条件:
go install google.golang.org/protobuf/cmd/protoc-gen-go
然后,使用提供的“.proto”文件运行protoc,我们将指示它在“go”目录下生成代码。
protoc --go_out=go proto_files/greeting.proto
执行上述命令后,您会注意到一个自动生成的类:“greeting.pb.go”。此类将帮助我们序列化和反序列化 Greet 对象。
现在,让我们创建数据的写入器,它将以用户名和问候语作为输入:
greeting_writer.go
import "fmt"
import "io/ioutil"
func main() {
greet := Greeting{}
greet.username = "John"
greet.greeting = "Hello"
out, err := proto.Marshal(greet)
ioutil.WriteFile("greeting_go_out", out , 0644)
fmt.Println("Saved greeting with following data to disk:")
fmt.Println(p)
}
现在让我们创建将读取文件的读取器:
greeting_reader.go
import "fmt"
import "io/ioutil"
func main() {
in, err := ioutil.ReadFile("greeting_go_out")
greet := &pb.Greet{}
proto.Unmarshal(in, greet)
fmt.Println("Reading from file greeting_protobuf_output:")
fmt.Println(greet)
}
输出
读取器只需从同一文件读取,将其反序列化,并打印有关问候语的数据。
现在我们已经设置了读取器和写入器,让我们编译项目。
接下来,让我们首先执行写入器:
go run greeting_writer.go
Saved greeting with following data to disk:
{greeting: Hello, username: John}
然后,让我们执行读取器:
go run greeting_reader.go
Reading from file greeting_protobuf_output
{greeting: Hello, username: John}
因此,正如我们所看到的,写入器序列化并保存到文件中的数据,这些数据被读取器正确地反序列化并相应地打印出来。
在 Dart 中使用 Google Protocol Buffers
要使用上述 Protocol Buffers 文件,我们首先必须安装并为 Dart 语言中的Greet类生成代码。为此,我们需要执行以下操作:
安装Dart Protocol Buffers 插件 (protoc-gen-go),这是我们一直在使用的protoc文件的先决条件。https://github.com/dart-lang/protobuf/tree/master/protoc_plugin#how-to-build-and-use
然后,使用提供的“.proto”文件运行protoc,我们将指示它在“dart”目录下生成代码。
protoc --go_out=dart proto_files/greeting.proto
执行上述命令后,您会注意到一个自动生成的类:“greeting.pb.dart”。此类将帮助我们序列化和反序列化Greet对象。
现在,让我们创建数据的写入器,它将以用户名和问候语作为输入:
greeting_writer.dart
import 'dart:io';
import 'dart/greeting.pb.dart';
main(List arguments) {
Greeting greet = Greeting();
greet.greeting = "Hello";
greet.username = "John";
File file = File("greeting_go_out");
print("Saved greeting with following data to disk:")
file.writeAsBytes(greet.writeToBuffer());
print(greet)
}
接下来,让我们创建一个将读取文件的读取器:
greeting_reader.dart
import 'dart:io';
import 'dart/greeting.pb.dart';
main(List arguments) {
File file = File("greeting_go_out");
print("Reading from file greeting_protobuf_output:")
Greeting greet = Greeting.fromBuffer(file.readAsBytesSync());
print(greet)
}
输出
读取器只是从同一个文件读取,反序列化它,并打印有关问候语的数据。
现在我们已经设置了读取器和写入器,让我们编译项目。
接下来,让我们首先执行写入器:
dart run greeting_writer.dart Saved greeting with following data to disk: greeting: Hello username: John
然后,让我们执行读取器。
dart run greeting_reader.dart Reading from file greeting_protobuf_output greeting: Hello username: John
所以,正如我们所看到的,由写入器序列化并保存到文件中的数据,这些数据被读取器正确地反序列化并相应地打印出来。