protobuf那些事(二)

Java基础

浏览数:50

2020-6-13

编译proto文件

上一篇,介绍了proto文件,以及用idea的工具编译了proto文件。当然,我们也可以直接用protoc命令来编译。

protoc -I=SRC_DIR –java_out=DST_DIR SRC_DIR/hello.proto

  1. protoc需要定位到protoc.exe目录,要么进入F:\soft\protoc-3.10.1-win64\bin的目录(以自己的环境为准),要么配置在系统环境变量path里。
  2. -I=IMPORT_PATH写法同--proto_path=IMPORT_PATH,用来指定proto文件目录,
  3. --java_out=指定生成的java文件存放的目录,java的类名同proto文件名,但是如果Message的名称和proto名称一样,比如上一篇的简单例子,proto文件名是Person.proto,Message也是Person,那类名就是PersonOutClass。如果是proto文件名是foo_bar.proto,那类名就是FooBar。这边要注意的是,文件夹是要存在的,protoc不会帮我们创建。

java文件

proto文件

syntax = "proto3";
package ch13;
option java_package = "com.example.ch13";
option java_outer_classname = "AddressBookProtos";

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2 ;
    }

    repeated PhoneNumber phones = 4;
}

message AddressBook {
    repeated Person person = 1;
}

生成java文件AddressBookProtos.java后,我们看看java类里有哪些内容。

PersonOrBuilder

PersonOrBuilder继承了com.google.protobuf.MessageOrBuilder,我们看看这个接口的几个方法,都是对应我们在proto文件中定义的字段,很像我们的javaBean,但是没有set方法,有get方法。
对于Repeated字段,还提供了其他的方法,比如通过索引查找、获取个数等。

// 对应着name
java.lang.String getName();
com.google.protobuf.ByteString getNameBytes();
// 对应着id
int getId();
// 对应着email
java.lang.String getEmail();
com.google.protobuf.ByteString getEmailBytes();
// 对应着phones
java.util.List<com.example.ch13.AddressBookProtos.Person.PhoneNumber> getPhonesList();
com.example.ch13.AddressBookProtos.Person.PhoneNumber  getPhones(int index);
int getPhonesCount();
java.util.List<? extends com.example.ch13.AddressBookProtos.Person.PhoneNumberOrBuilder> getPhonesOrBuilderList();
com.example.ch13.AddressBookProtos.Person.PhoneNumberOrBuilder getPhonesOrBuilder(int index);

Person

Person继承了com.google.protobuf.GeneratedMessageV3,还实现了上面的PersonOrBuilder接口。
Person类中定义了一个枚举PhoneType:

Message中嵌套了PhoneNumber,所以Person中包含了PhoneNumberOrBuilder和PhoneNumber,这两个结构同PersonOrBuilder和Person:

Person类中还包含了重要的Builder,这个后面在详细说。

除了上面几个枚举、接口、类,还包含了几个方法:
getDefaultInstance:返回Person的单例。等同于Person.newBuilder().build()

public static com.example.ch13.AddressBookProtos.Person getDefaultInstance() {
  return DEFAULT_INSTANCE;
}

parseFrom(...):解析给定的数据源,转成Message对象的Person。
newBuilder:创建一个build

Builder

Builder继承了com.google.protobuf.GeneratedMessageV3.Builder<Builder>,还实现了PersonOrBuilder接口。
主要有构建类,对字段的set、get、clear等操作。

实践

先获取Builder,然后对各个实例进行set或者add操作,最后build返回一个AddressBook,通过AddressBook的writeTo方法,把序列号的值写入文件。
AddPerson

class AddPerson {
    public static void main(String[] args) throws Exception {
        AddressBook.Builder builder =AddressBook.newBuilder().addPerson(Person.newBuilder().setName("aa")
                .setId(1)
                .setEmail("123@qq.com")
                .addPhones(Person.PhoneNumber.newBuilder()
                        .setNumber("111")
                        .setType(Person.PhoneType.HOME))
                .addPhones(Person.PhoneNumber.newBuilder()
                        .setNumber("222")
                        .setType(Person.PhoneType.MOBILE))).addPerson(Person.newBuilder().setName("bb")
                .setId(2)
                .setEmail("456@qq.com")
                .addPhones(Person.PhoneNumber.newBuilder()
                        .setNumber("333")
                        .setType(Person.PhoneType.HOME))
                .addPhones(Person.PhoneNumber.newBuilder()
                        .setNumber("444")
                        .setType(Person.PhoneType.MOBILE)));
        AddressBook addressBook = builder.build();
        PersonList.Print(addressBook);
        addressBook.writeTo(new FileOutputStream("f:\\1.txt"));
    }
}

PersonList

class PersonList {
    static void Print(AddressBook addressBook) {
        for (Person person: addressBook.getPersonList()) {
            System.out.println("Person ID: " + person.getId());
            System.out.println("  Name: " + person.getName());
            System.out.println("  E-mail address: " + person.getEmail());

            for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
                switch (phoneNumber.getType()) {
                    case MOBILE:
                        System.out.print("  Mobile phone #: ");
                        break;
                    case HOME:
                        System.out.print("  Home phone #: ");
                        break;
                    case WORK:
                        System.out.print("  Work phone #: ");
                        break;
                }
                System.out.println(phoneNumber.getNumber());
            }
        }
    }
}

运行结果:

ListPeople,通过parseFrom读取

class ListPeople {
    public static void main(String[] args) throws Exception {
        AddressBook addressBook =
                AddressBook.parseFrom(new FileInputStream("f:\\1.txt"));
        PersonList.Print(addressBook);
    }
}

运行结果同上。

合并

public class MergePerson {
    public static void main(String[] args) {
        Person person1 =Person.newBuilder().setId(1).addPhones(Person.PhoneNumber.newBuilder().setNumber("111").setType(Person.PhoneType.MOBILE)).build();
        Person person2 =Person.newBuilder().setId(2).addPhones(Person.PhoneNumber.newBuilder().setNumber("222").setType(Person.PhoneType.WORK)).build();
        person1 = person1.toBuilder().mergeFrom(person2).build();
        System.out.println("Person ID: " + person1.getId());
        for (Person.PhoneNumber phoneNumber : person1.getPhonesList()) {
            switch (phoneNumber.getType()) {
                case MOBILE:
                    System.out.print("  Mobile phone #: ");
                    break;
                case HOME:
                    System.out.print("  Home phone #: ");
                    break;
                case WORK:
                    System.out.print("  Work phone #: ");
                    break;
            }
            System.out.println(phoneNumber.getNumber());
        }
    }
}

运行结果如下:

可以看出id被后面的覆盖了,而PhoneNumber是累加的

作者:大军