Site Overlay

Java 对 Protobuf 字节数组的动态解析

前提条件

  • 统一接口
  • 多用户、多Protobuf描述文件
  • 实时解析

方案

protobuf 文件示例:

//指定protobuf语法版本
syntax = "proto3";

//包名
option java_package = "com.test.protobuf.demo.protobufBean";
//源文件类名
option java_outer_classname = "ProtobufPerson";

// class Person
message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
  Dog dog = 4;

}

message Dog {
  string dogName = 4;
  int32 id = 5;
  string email = 6;
}
一、利用描述文件

利用 protobuf 文件,生成 descriptor 文件

protoc Test.proto --descriptor_set_out=Test.description

java 代码:

ProtobufPerson.Person.Builder personBuilder = ProtobufPerson.Person.newBuilder();

            personBuilder.setId(10);
            personBuilder.setName("Tom");
            personBuilder.setEmail("123@123.com");
            personBuilder.setDog(ProtobufPerson.Dog.newBuilder().setDogName("456").setId(15).setEmail("456@456").build());

            ProtobufPerson.Person reqPerson = personBuilder.build();

            System.out.println(reqPerson.toString());

            byte[] dataByte = personBuilder.build().toByteArray();

            System.out.println(new String(dataByte));
            // 原路解析
            ProtobufPerson.Person respPerson = ProtobufPerson.Person.parseFrom(dataByte);
            System.out.println(respPerson.toString());

            Descriptors.Descriptor pbDescriptor = null;
            DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet
                    .parseFrom(new FileInputStream("/Users/xxx/protobuf-demo/src/proto/Test.description"));
            System.out.println(descriptorSet.getFileList().size());
            for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
                Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fdp, new Descriptors.FileDescriptor[] {});
                for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
// 需要 知道描述文件里,你需要用那个; 接口传或者 protobuf文件只有一个 message,不然就无法对应
                    if (descriptor.getName().equals("Person")) {
                        System.out.println("Movie descriptor found");
                        pbDescriptor = descriptor;
                        break;
                    }
                }
            }

            if (pbDescriptor == null) {
                System.out.println("No matched descriptor");
                return;
            }
            DynamicMessage.Builder pbBuilder = DynamicMessage.newBuilder(pbDescriptor);
            Message pbMessage = pbBuilder.mergeFrom(dataByte).build();

缺点: 需要加载 descriptor 文件,并且需要接口传描述文件里的实体 名称

二、利用统一的protobuf消息体

方案一的麻烦点在于,服务端需要自己有一份protobuf的 descriptor 文件。

消息体protobuf文件示例:

syntax = "proto3";

option java_package="com.test.protobuf.demo.protobufBean";

import "google/protobuf/descriptor.proto";

message SelfDescribingMessage {
  // Set of FileDescriptorProtos which describe the type and its dependencies.
  google.protobuf.FileDescriptorSet descriptor_set = 1;

  string msg_name=2;

  // The message and its type, encoded as an Any message.
  bytes message = 3;
}

java 代码:

ProtobufPerson.Person.Builder personBuilder = ProtobufPerson.Person.newBuilder();

            personBuilder.setId(10);
            personBuilder.setName("Tom");
            personBuilder.setEmail("123@123.com");
            personBuilder.setDog(ProtobufPerson.Dog.newBuilder().setDogName("456").setId(15).setEmail("456@456").build());

            ProtobufPerson.Person reqPerson = personBuilder.build();

            DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet
                    .parseFrom(new FileInputStream("/Users/xxx/tctWorkspace/protobuf-demo/src/proto/Test.description"));

            Selfmd.SelfDescribingMessage.Builder selfmdBuilder = Selfmd.SelfDescribingMessage.newBuilder();
            selfmdBuilder.setDescriptorSet(descriptorSet);
            selfmdBuilder.setMsgName(ProtobufPerson.Person.getDescriptor().getFullName());
            selfmdBuilder.setMessage(reqPerson.toByteString());
            Selfmd.SelfDescribingMessage build = selfmdBuilder.build();
            byte[] byteArray = build.toByteArray();

            // 消费者
            long currentTime = System.currentTimeMillis();
            Selfmd.SelfDescribingMessage parseFrom = Selfmd.SelfDescribingMessage.parseFrom(byteArray);
            DescriptorProtos.FileDescriptorSet descriptorSet2 = parseFrom.getDescriptorSet();
            ByteString message = parseFrom.getMessage();
            String msgName = parseFrom.getMsgName();

            Descriptors.Descriptor pbDescritpor = null;
            for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet2.getFileList()) {
                Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fdp,
                        new Descriptors.FileDescriptor[] {});
                for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
                    if (descriptor.getName().equals(msgName)) {
                        System.out.println("descriptor found");
                        pbDescritpor = descriptor;
                        break;
                    }
                }
            }

            if (pbDescritpor == null) {
                System.out.println("No matched descriptor");
                return;
            }
            DynamicMessage dmsg = DynamicMessage.parseFrom(pbDescritpor, message);
            JsonFormat jsonFormat = new JsonFormat();
            String dataStr = jsonFormat.printToString(dmsg);
            System.out.println(dataStr);

关于解析成 protobuf的message对象后,可以用google的转换 jar包,把它转换成json,完全不需要别的操作。

<!-- https://mvnrepository.com/artifact/com.googlecode.protobuf-java-format/protobuf-java-format -->
        <dependency>
            <groupId>com.googlecode.protobuf-java-format</groupId>
            <artifactId>protobuf-java-format</artifactId>
            <version>1.4</version>
        </dependency>

方案二,比较适合做一些平台型的。使用方案一的话,不如每次添加新的protobuf的java文件,然后重启。

0

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据