Protocol Buffers - 复合数据类型



概述

还有两种复合数据类型可能对复杂的用例很有用。它们是“oneof”“any”。在本章中,我们将学习如何使用这两种 Protobuf 数据类型。

oneof

我们将一些参数传递给此oneof数据类型,Protobuf确保只有一个参数被设置。如果我们设置其中一个,并尝试设置另一个,第一个属性将被重置。让我们通过一个例子来理解这一点。

继续我们来自Protocol Buffers - 字符串章节的theater示例,假设我们有一个用于获取可用员工数量的API。从该API返回的值随后被设置为以下文件中的'count'标签。但是,如果该API出错,我们无法真正获得'count',而是附加错误日志。

理想情况下,我们总是会设置其中一个,即调用成功并且我们得到计数,或者计数计算失败并且我们得到错误消息。

以下是我们需要使用的语法,以指示 Protobuf 我们将创建一个oneof属性:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater { 
   oneof availableEmployees {
      int32 count = 4;
      string errorLog = 5;
   }
}

使用Java进行序列化

要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:

protoc  --java_out=. theater.proto

这将在当前目录的com > tutorialspoint > theater文件夹中创建一个TheaterOuterClass.java类。我们像在Protocol Buffers - 基本应用章节中所做的那样,在我们的应用程序中使用此类。

使用从Proto文件创建的Java类

首先,我们将创建一个写入器来写入剧院信息:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
    	   
      Theater theater = Theater.newBuilder()
         .setCount(3)
         .setErrorLog("No employee found")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下来,我们将有一个读取器来读取剧院信息:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

编译项目

现在我们已经设置了读取器写入器,让我们编译项目。

mvn clean install

序列化Java对象

现在,编译后,让我们首先执行写入器

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
errorLog: "No employee found"

反序列化序列化对象

现在,让我们执行读取器从同一文件读取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
errorLog: "No employee found"

因此,正如我们所看到的,我们只能看到后来设置的错误日志。

Any

下一个可用于复杂用例的数据类型是Any。我们可以将任何类型/消息/类传递给此数据类型,Protobuf 不会报错。让我们通过一个例子来理解这一点。

继续使用剧院示例,假设我们想要跟踪剧院内的人员。其中一些可能是员工,另一些可能是观众。但最终他们都是人,所以我们将把他们放在一个包含两种类型的列表中。

现在我们的类/消息包含一个Any属性'peopleInside'列表以及ViewerEmployee类,即剧院内人员的信息。让我们看看它的实际应用。

以下是我们需要使用的语法,以指示 Protobuf 我们将创建一个Any属性:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

import "google/protobuf/any.proto";

message Theater {
   string name = 1;
   string address = 2;
  
   repeated google.protobuf.Any peopleInside = 3;
}
message Employee{
   string name = 1;
   string address = 2;
}
message Viewer{
   string name = 1;
   int32 age = 2;
   string sex = 3;
}

使用Java进行序列化

要使用 Protobuf,我们现在必须使用protoc二进制文件从此“.proto”文件创建所需的类。让我们看看如何做到这一点:

protoc  --java_out=. theater.proto

这将在当前目录的com > tutorialspoint > theater文件夹中创建一个TheaterOuterClass.java类。我们像在Protocol Buffers - 基本应用章节中所做的那样,在我们的应用程序中使用此类。

使用从Proto文件创建的Java类

首先,我们将创建一个写入器来写入剧院信息:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
	    
   List<Any> people = new ArrayList<>();
   people.add(Any.pack(Employee.newBuilder().setName("John").build()));
   people.add(Any.pack(Viewer.newBuilder().setName("Jane").setAge(30).build()));
   people.add(Any.pack(Employee.newBuilder().setName("Simon").build()));
   people.add(Any.pack(Viewer.newBuilder().setName("Janice").setAge(25).build()));
	    
   Theater theater = Theater.newBuilder()
      .setName("SilverScreen")
      .addAllPeopleInside(people)
      .build();
		
      String filename = "theater_protobuf_output_silver";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下来,我们将有一个读取器来读取剧院信息:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
	   Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output_silver";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
            
         System.out.println("Name:" + theater.getName() + "\n");
            
         for (Any anyPeople : theater.getPeopleInsideList()) {
            if(anyPeople.is(Employee.class)) {
               Employee employee = anyPeople.unpack(Employee.class);
               System.out.println("Employee:" + employee + "\n");
            }
                
            if(anyPeople.is(Viewer.class)) {
               Viewer viewer = anyPeople.unpack(Viewer.class);
               System.out.println("Viewer:" + viewer + "\n");
            }
         }
      }
   }
}	

编译项目

现在我们已经设置了读取器写入器,让我们编译项目。

mvn clean install

序列化Java对象

现在,编译后,让我们首先执行写入器

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "SilverScreen"
peopleInside {
   type_url: "type.googleapis.com/theater.Employee"
   value: "\n\004John"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Viewer"
   value: "\n\004Jane\020\036"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Employee"
   value: "\n\005Simon"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Viewer"
   value: "\n\006Janice\020\031"
}

注意 - 需要注意两点:

  • 对于Any,Protobuf 将任何标签内的内容打包/序列化为字节,然后将其存储为'value'。基本上,这允许我们使用此'Any'标签发送任何消息类型。

  • 我们还看到了"type.googleapis.com/theater.Viewer""type.googleapis.com/theater.Employee"。Protobuf 使用它来保存对象类型以及数据,因为Any数据类型的类型可能有所不同。

反序列化序列化对象

现在让我们执行读取器从同一文件读取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output_silver
Name:SilverScreen

Employee:name: "John"

Viewer:name: "Jane"
age: 30

Employee:name: "Simon"

Viewer:name: "Janice"
age: 25

因此,正如我们所看到的,我们的读取器代码能够成功地区分EmployeeViewer,即使它们出现在同一个数组中。

广告