CLOVER🍀

That was when it all began.

Protocol Buffersを試してみる

ちょっと理由があって、Protocol Buffersを試してみることにしました。

Procol Buffers
https://developers.google.com/protocol-buffers/?hl=ja

この手のシリアライゼーション系のライブラリは、ほぼ使ったことがありません。XMLとかJSONならまあ、ですが…。Protocol Buffers以外にも、世の中にはいろいろある様子。

MessagePack、Kryo、Protocol Buffersなどのシリアライザーのパフォーマンス比較
http://blog.katty.in/4567

Protocol Buffersは遅い
http://frsyuki.hatenablog.com/entry/20081116/p1

MessagePackが有名なのかなぁ?

で、今回試すProtocol Buffersですが、以下のような情報を参考にさせていただきました。

複雑なデータをXMLより効率よく送る!〜Protocol Buffers編
http://www.acroquest.co.jp/webworkshop/JavaNetwork/NP_lecture07.html
複雑なデータをXMLより効率よく送る!〜Protocol Buffers編
http://www.acroquest.co.jp/webworkshop/JavaNetwork/NP_lecture08.html
Protocol Buffersの使い方まとめ
http://d.hatena.ne.jp/hrendoh/20110713/1310549732

その他、本家の以下の情報も。

Protocol Buffer Basics: Java
https://developers.google.com/protocol-buffers/docs/javatutorial?hl=ja
Language Guide
https://developers.google.com/protocol-buffers/docs/proto?hl=ja
Java Generated Code
https://developers.google.com/protocol-buffers/docs/reference/java-generated?hl=ja

それでは、進めてみます。

コンパイラのインストール

まずは、以下よりソースコードをダウンロード。

http://code.google.com/p/protobuf/downloads/list

2013年2月の2.5.0が最新なんですね。

こちらから、今回は「protobuf-2.5.0.tar.gz」をダウンロードしました。

展開。

$ tar -zxvf protobuf-2.5.0.tar.gz
$ cd protobuf-2.5.0

gccとg++がなければ、追加でインストール。

$ sudo apt-get install gcc g++

あ、当方、環境はUbuntu Linuxでやっております。

インストール先は、適当なローカルディレクトリとします。

$ ./configure --prefix=/xxxxx/protoc-local
$ make
$ make install

今回は、「protoc-local」ディレクトリとしました。

「protoc-local」ディレクトリがカレントディレクトリにある場所に移動して、確認。

$ ./protoc-local/bin/protoc --version
libprotoc 2.5.0

はい。

データ構造を定義する

Protocol Buffersでデータ構造を定義するには、「.proto」という拡張子のファイルを作成し、先ほどインストールしたprotocコマンドでソースコードを生成するみたいです。

で、作成したprotoファイル。「proto-src」というディレクトリに、今回は置きました。
proto-src/person.proto

package example;

option java_package = "org.littlewings.protobuf.example";
option java_outer_classname = "Person";

message PersonData {
  required int32 id = 1;
  required string last_name = 2;
  required string first_name = 3;
  optional int32 age = 4;
}

optionで指定している「java_outer_classname」とmessageの名前は、同じにすることはできません。

では、コンパイルします。出力先のトップレベルのディレクトリは、あらかじめ作成しておく必要があります。

$ mkdir proto-out

今回は、このような名前のディレクトリに出力することにします。

コンパイル。

$ ./protoc-local/bin/protoc --java_out=proto-out proto-src/person.proto

こんな感じのパスで、ソースコードが生成されます。

proto-out/org/littlewings/protobuf/example/Person.java

中身は、必要であれば見る感じでしょうけど、修正することはないと思います。

使ってみる

それでは、生成したソースコードを使ってシリアライズ/デシリアライズを試してみましょう。

先ほど生成したソースコードを、Mavenのソースディレクトリに放り込みます。

$ cp -R proto-out/org src/main/java/

まあ、いきなりsrc/main/javaに出力するのもありかもですが。

Maven依存関係には、Protocol Buffersを加えます。

    <dependency>
      <groupId>com.google.protobuf</groupId>
      <artifactId>protobuf-java</artifactId>
      <version>2.5.0</version>
    </dependency>

今回はテストコードをして試すので、JUnitとFest Assertionsも加えます。

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.easytesting</groupId>
      <artifactId>fest-assert-core</artifactId>
      <version>2.0M10</version>
      <scope>test</scope>
    </dependency>

シリアライズとデシリアライズのテスト。

    @Test
    public void testProtoBufSimple() throws IOException {
        // データ作成
        Person.PersonData.Builder pb = Person.PersonData.newBuilder();
        pb.setId(1);
        pb.setFirstName("カツオ");
        pb.setLastName("磯野");
        pb.setAge(11);

        Person.PersonData person = pb.build();

        // シリアライズ
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        person.writeTo(baos);

        // デシリアライズ
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        Person.PersonData.Builder pb2 = Person.PersonData.newBuilder();
        pb2.mergeFrom(bais);

        // 確認
        Person.PersonData deserialized = pb2.build();
        assertThat(deserialized.getId())
            .isEqualTo(1);
        assertThat(deserialized.getFirstName())
            .isEqualTo("カツオ");
        assertThat(deserialized.getLastName())
            .isEqualTo("磯野");
        assertThat(deserialized.getAge())
            .isEqualTo(11);
    }

必須フィールドがなかった場合は、シリアライズできません。

    @Test
    public void testRequired() {
        try {
            Person.PersonData.Builder pb = Person.PersonData.newBuilder();
            Person.PersonData person = pb.build();

            failBecauseExceptionWasNotThrown(com.google.protobuf.UninitializedMessageException.class);
        } catch (Exception e) {
            assertThat(e)
                .isInstanceOf(com.google.protobuf.UninitializedMessageException.class)
                .hasMessage("Message missing required fields: id, last_name, first_name");
        }
    }

optionalなフィールドは、指定しなくてもOK。

    @Test
    public void testOptional() {
        Person.PersonData.Builder pb = Person.PersonData.newBuilder();
        pb.setId(2);
        pb.setFirstName("ワカメ");
        pb.setLastName("磯野");

        Person.PersonData person = pb.build();

        assertThat(person.getAge())
            .isEqualTo(0);
    }

けっこう身構えてたんですけど、思っていたよりも簡単に使えそうです。