如何解析协议缓冲区FileDescriptor

时间:2017-03-07 15:29:21

标签: java protocol-buffers

我正在使用Java协议缓冲区2.5。我有一个定义自定义选项的proto文件。另一个proto文件使用该自定义选项。如果我保留相应的FileDescriptorProto然后读取它们并将它们转换为FileDescriptors,则对自定义选项的引用将显示为未知字段。如何正确解析自定义选项?

这是代码。我有两个.proto文件。 protobuf-options.proto看起来像这样:

package options;

import "google/protobuf/descriptor.proto";

option java_package = "com.example.proto";
option java_outer_classname = "Options";

extend google.protobuf.FieldOptions {
    optional bool scrub = 50000;
}

导入的google/protobuf/descriptor.proto正是协议缓冲区2.5附带的descriptor.proto

example.proto看起来像这样:

package example;
option java_package = "com.example.protos";
option java_outer_classname = "ExampleProtos";
option optimize_for = SPEED;
option java_generic_services = false;

import "protobuf-options.proto";

message M {
    optional int32 field1 = 1;
    optional string field2 = 2 [(options.scrub) = true];
}

如您所见,field2引用了protobuf-options.proto定义的自定义选项。

以下代码将所有三个protos的二进制编码版本写入/tmp

package com.example;

import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.example.protos.ExampleProtos;

import java.io.FileOutputStream;
import java.io.OutputStream;

/**
 *
 */
public class PersistFDs {
    public void persist(final FileDescriptor fileDescriptor) throws Exception {
        System.out.println("persisting "+fileDescriptor.getName());
        try (final OutputStream outputStream = new FileOutputStream("/tmp/"+fileDescriptor.getName())) {
            final FileDescriptorProto fileDescriptorProto = fileDescriptor.toProto();
            final ByteString byteString = fileDescriptorProto.toByteString();
            byteString.writeTo(outputStream);
        }
        for (final FileDescriptor dependency : fileDescriptor.getDependencies()) {
            persist(dependency);
        }
    }
    public static void main(String[] args) throws Exception {
        final PersistFDs self = new PersistFDs();
        self.persist(ExampleProtos.getDescriptor());
    }
}

最后,以下代码从/tmp加载那些原型,将它们转换回FileDescriptors,然后检查field2上的自定义选项:

package com.example;

import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.UnknownFieldSet.Field;

import java.io.FileInputStream;
import java.io.InputStream;

/**
 *
 */
public class LoadFDs {
    public FileDescriptorProto loadProto(final String filePath) throws Exception {
        try (final InputStream inputStream = new FileInputStream(filePath)) {
            final ByteString byteString = ByteString.readFrom(inputStream);
            final FileDescriptorProto result = FileDescriptorProto.parseFrom(byteString);

            return result;
        }
    }

    public static void main(final String[] args) throws Exception {
        final LoadFDs self = new LoadFDs();

        final FileDescriptorProto descriptorFDProto = self.loadProto("/tmp/google/protobuf/descriptor.proto");
        final FileDescriptorProto optionsFDProto = self.loadProto("/tmp/protobuf-options.proto");
        final FileDescriptorProto fakeBoxcarFDProto = self.loadProto("/tmp/example.proto");

        final FileDescriptor fD = FileDescriptor.buildFrom(descriptorFDProto, new FileDescriptor[0]);
        final FileDescriptor optionsFD = FileDescriptor.buildFrom(optionsFDProto, new FileDescriptor[] { fD });
        final FileDescriptor fakeBoxcarFD = FileDescriptor.buildFrom(fakeBoxcarFDProto, new FileDescriptor[] { optionsFD });

        final FieldDescriptor optionsFieldDescriptor = optionsFD.findExtensionByName("scrub");
        if (optionsFieldDescriptor == null) {
            System.out.println("Did not find scrub's FieldDescriptor");
            System.exit(1);
        }
        final FieldDescriptor sFieldDescriptor = fakeBoxcarFD.findMessageTypeByName("M").findFieldByName("field2");
        System.out.println("unknown option fields "+sFieldDescriptor.getOptions().getUnknownFields());
        final boolean hasScrubOption = sFieldDescriptor.getOptions().hasField(optionsFieldDescriptor);
        System.out.println("hasScrubOption: "+hasScrubOption);

    }
}

当我运行LoadFDs时,它会因此异常而失败:

  

未知选项字段50000:1

     

线程“main”中的异常java.lang.IllegalArgumentException:FieldDescriptor与消息类型不匹配。     在com.google.protobuf.GeneratedMessage $ ExtendableMessage.verifyContainingType(GeneratedMessage.java:812)     在com.google.protobuf.GeneratedMessage $ ExtendableMessage.hasField(GeneratedMessage.java:761)     在com.example.LoadFDs.main(LoadFDs.java:42)

FieldDescriptor字段的s选项应该包含该自定义选项的字段,但它具有未知字段。未知字段的字段编号和值是正确的。只是自定义选项没有得到解决。我该如何解决这个问题?

1 个答案:

答案 0 :(得分:0)

您需要使用 FileDescriptorProto.parseFrom(byte[] data, ExtensionRegistryLite extensionRegistry,并明确创建一个 ExtentionRegistry。这是创建扩展注册表的一种方法:

ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
com.example.proto.Options.registerAllExtensions(extensionRegistry);

(其中 com.example.proto.Options 是编译的自定义选项类)

这显然只有在您有权访问客户端的自定义选项编译文件时才有效。不知道有没有办法在客户端序列化扩展和反序列化。