Protobuf動態解析在Java中的應用 包含例子程序


最近在做ProtoBuf相關的項目,其中用到了動態解析,網上看了下相關資料和博文都比較少,自己來寫一個記錄一下學習過程。
 
Protocol Buffers是結構化數據格式標准,提供序列化和反序列方法,用於存儲和交換。語言中立,平台無關、可擴展。目前官方提供了C++、Java、Python API,也有其他語言的開源api(比如php)。 可通過 .proto 文件生成對應語言的類代碼
如果已知protobuf內容對應的是哪個類對象,則可以直接使用反序列化方法搞定(Xxx.parseFrom(inputStream)由二進制轉換,TextFormat.merge(string, xxxBuilder)由文本轉換)
 
而我們經常遇到的情況是,拿到一個被protobuf序列化的二進制內容,但不知道它的類型,無法獲得對應的類對象。這種多見於需要處理各種各樣未知的ProtoBuf對象的系統。ProtoBuf提供了動態解析機制來解決這個問題,它要求提供二進制內容的基礎上,再提供對應類的Descriptor對象,在解析時通過DynamicMessage類的成員方法來獲得對象結果。
最后問題就是Descriptor對象從哪里來?這是通過protoc --descriptor_set_out=$outputpath 命令生成descriptor文件,進而得到的。代碼如下:
 1 option java_package="com.liulei.cinema";
 2 
 3 enum MovieType{
 4     CHILDREN=1;
 5     ADULT=2;
 6     NORMAL=3;
 7     OHTER=4;
 8 }
 9 
10 enum Gender{
11     MAN=1;
12     WOMAN=2;
13     OTHER=3;
14 }
15 
16 message Movie{
17     required string name=1;
18     required MovieType type=2;
19     optional int32 releaseTimeStamp=3;
20     optional string description=4;
21 }
22 
23 message Customer{
24     required string name=1;
25     optional Gender gender=2;
26     optional int32 birthdayTimeStamp=3;
27 }
28 
29 message Ticket{
30     required int32 id=1;
31     required Movie movie=2;
32     required Customer customer=3;
33 }
cinema.proto
 1 public static void main( String[] args ) {
 2 
 3         Cinema.Movie.Builder movieBuilder = Cinema.Movie.newBuilder();
 4         movieBuilder.setName("The Shining");
 5         movieBuilder.setType(Cinema.MovieType.ADULT);
 6         movieBuilder.setReleaseTimeStamp(327859200);
 7 
 8         System.out.println("Dynamic Message Parse by proto file");
 9         try {
10             byte[] buffer3 = new byte[movieBuilder.build().getSerializedSize()];
11             CodedOutputStream codedOutputStream3 = CodedOutputStream.newInstance(buffer3);
12             try {
13                 movieBuilder.build().writeTo(codedOutputStream3);
14                 System.out.println(buffer3);
15             } catch (IOException e) {
16                 e.printStackTrace();
17             }
18             String protocCMD = "protoc --descriptor_set_out=cinema.description ./cinema.proto --proto_path=.";
19             Process process = Runtime.getRuntime().exec(protocCMD);
20             process.waitFor();
21             int exitValue = process.exitValue();
22             if (exitValue != 0) {
23                 System.out.println("protoc execute failed");
24                 return;
25             }
26             Descriptors.Descriptor pbDescritpor = null;
27             DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(new FileInputStream("./cinema.description"));
28             for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
29                 Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fdp, new Descriptors.FileDescriptor[]{});
30                 for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
31                     if (descriptor.getName().equals("Movie")) {
32                         System.out.println("Movie descriptor found");
33                         pbDescritpor = descriptor;
34                         break;
35                     }
36                 }
37             }
38             if (pbDescritpor == null) {
39                 System.out.println("No matched descriptor");
40                 return;
41             }
42             DynamicMessage.Builder pbBuilder = DynamicMessage.newBuilder(pbDescritpor);
43 
44             Message pbMessage = pbBuilder.mergeFrom(buffer3).build();
45             System.out.println(pbMessage);
46 
47         } catch (Exception e) {
48             System.out.println("Exception");
49             e.printStackTrace();
50         }
51     }
Main.java
執行結果:

Dynamic Message Parse From byte array
[B@597ccf6e
Movie descriptor found
name: "The Shining"
type: ADULT
releaseTimeStamp: 327859200

 
解釋具體過程:
0.首先對.proto文件使用protoc命令,生成的descriptor文件中包含多個類對應的descriptor類信息(序列化的DescriptorSet內容)
1.首先取出序列化的DescriptorSet內容,FileDescriptorSet.parseFrom方法反序列化得到FileDescriptorSet對象
2.取出對應message類型的Descriptor。
     DescriptorSet成員方法getFileList(),拿到多個FileDescriptorProto對象,再構建對應FileDescriptor。
     FileDescriptor的成員方法getMessageTypes()得到所有Message的Descriptor對象,找到對應名字的Descriptor
3.用Descriptor對象反序列化對象
     構建DynamicMessage.Builder對象builder,再調用builder的mergeFrom/merge方法得到Message對象
 
其中Descriptor相關類:

DescriptorProtos.FileDescriptorSet:protoc編譯出來類文件中包含這個類,描述多個.proto文件中的類

DescriptorProtos.FileDescriptorProto:描述一個完整的.proto文件中的類
DescriptorProtos.FileDescriptor:由DescriptorProtos.FileDescriptorProto構建而來(buildFrom),描述1個完整.proto文件中的所有內容,包括message類型的Descriptor和其他被導入文件的Descriptor。
      getMessageTypes()方法:返回 List< Descriptors.Descriptor>。得到FileDescriptor內,所有message類型直接兒子的Descriptor列表    
DescriptorProtos.Descriptor:描述一個message類型,通過getName()得到message的類名
 
 轉載請注明出處。歡迎拍磚~
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM