Protobuf - 复合数据类型

还有两种复合数据类型可能对复杂的用例有用。它们是"OneOf""Any"。在本章中,我们将了解如何使用 Protobuf 的这两种数据类型。

OneOf

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

继续我们的剧院示例,例如,我们有一个用于获取可用员工数量的 API。然后,从此 API 返回的值在以下文件中设置为"count"标签。但是如果该 API 出错,我们就无法真正"计数",而是附加错误日志。

理想情况下,我们始终会设置其中一个,即,要么调用成功,我们得到计数,要么计数计算失败,我们得到错误消息。

以下是我们需要指示 Protobuf 创建 OneOf 属性的语法 −

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

message Theater {
   string name = 1;
   string address = 2;
  
   repeated google.protobuf.Any peopleInside = 3;
  
   oneof availableEmployees {
      int32 count = 4;
      string errorLog = 5;
   }
}

现在我们的 class/message 包含一个 OneOf 属性,即有关可用员工的信息。

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

protoc --java_out=java/src/main/java proto_files heater_advanced.proto

上述命令应创建所需的文件,现在我们可以在 Java 代码中使用它。首先,我们将创建一个 writer 来写入 theater 信息 −

package com.tutorialspoint.theater;

import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterAdvanced.Employee;
import com.tutorialspoint.theater.TheaterAdvanced.Viewer;
import com.tutorialspoint.theater.TheaterAdvanced.Theater;

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

接下来,我们将有一个reader(阅读器)来读取theater(剧院)的信息 −

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;
import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterAdvanced.Theater;
import com.tutorialspoint.theater.TheaterAdvanced.Theater.AvailableEmployeesCase;
import com.tutorialspoint.theater.TheaterAdvanced.Theater.Builder;
import com.tutorialspoint.theater.TheaterAdvanced.Viewer;
import com.tutorialspoint.theater.TheaterAdvanced.Employee;

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

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

现在,编译后,让我们首先执行writer

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

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

现在,让我们执行 reader 来读取同一个文件 −

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

Reading from file theater_protobuf_output_silver
Name:SilverScreen

Employee:name: "John"

Viewer:name: "Jane"
age: 30

Employee:name: "Simon"

Viewer:name: "Janice"
age: 25

因此,正如我们所见,在列表中,我们能够找出 Any 类型并找到相应的基础数据类型employee/viewer。 现在让我们看看defaultsAnyOf

任何

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

继续以剧院为例,我们想要跟踪剧院内的人员。 其中一些可能是员工,其他人可能是观众。但最终他们还是人,所以我们将把他们传递到一个包含这两种类型的列表中。

以下是我们需要用来指示 Protobuf 创建列表的语法 −

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

import "google/protobuf/any.proto";

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

现在我们的 class/message 包含一个 Any 属性 'peopleInside' 列表以及 ViewerEmployee 类,即有关剧院内人员的信息。让我们看看实际操作。

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

protoc --java_out=java/src/main/java proto_files heater_advanced.proto

上述命令应创建所需的文件,现在我们可以在 Java 代码中使用它。首先,我们将创建一个 writer 来写入 theater 信息 −

package com.tutorialspoint.theater;

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

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

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

接下来,我们将有一个reader(阅读器)来读取theater(剧院)的信息 −

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;
import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterAdvanced.Theater;
import com.tutorialspoint.theater.TheaterAdvanced.Theater.AvailableEmployeesCase;
import com.tutorialspoint.theater.TheaterAdvanced.Theater.Builder;
import com.tutorialspoint.theater.TheaterAdvanced.Viewer;
import com.tutorialspoint.theater.TheaterAdvanced.Employee;

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

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

现在,编译后,让我们首先执行writer

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

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

注意 − 有两点需要注意 −

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

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

现在让我们执行reader来读取同一个文件 −

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

Reading from file theater_protobuf_output_silver
Name:SilverScreen

Employee:name: "John"

Viewer:name: "Jane"
age: 30

Employee:name: "Simon"

Viewer:name: "Janice"
age: 25

因此,如我们所见,我们的 reader 代码能够成功区分 EmployeeViewer,即使它们位于同一个数组中。