ちょっと理由があって、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); }
けっこう身構えてたんですけど、思っていたよりも簡単に使えそうです。