- 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.tw/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
所以,正如我們所看到的,由寫入器序列化並儲存到檔案中的資料,這些資料被讀取器正確地反序列化並相應地打印出來。