MessagePack for Java ついに 0.6 リリース!

先日の fluent に引き続き、新しいソフトウェアのリリースです。
満を持して、MessagePack for Java 0.6 をリリースしました! 9ヶ月ぶりのメジャーバージョンアップです。


以前のバージョン 0.5 の API をすべて見直し、互換性を維持しながらも、数多くの機能を新たに搭載しました。動的オブジェクトAPIリフレクション機能の強化JRubyとの連携JSONサポート などなど。もちろん、性能も大きく向上しています。

このバージョン 0.6 のリリースによって、MessagePack の応用範囲は大きく広がります。MessagePackは、クラウドシステムからモバイルデバイスデバイスまで、多種多様なシステムの連携と統合をサポートする、新しいデータ表現形式です。


さて、新機能の詳細をご覧下さい:

JSONシリアライザ・デシリアライザを統合

MessagePack の型システムは JSON と互換性があり、言い換えれば、MessagePack は「速いJSON」「コンパクトなJSON」として利用することができます。既にJSONを利用しているシステムや、手軽にJSONを使いたいが性能が心配だというケースでは、MessagePackを利用することで高速化を図ることができます。

とは言え、「MessagePack に完全に依存するのはちょっと厳しいので、JSONも同時に利用したい」というケースは多いと思います。


新しい MessagePack for Java 0.6 では、JSONを扱うことができます。JSONとMessagePackを両方サポートし、性能が重要な場面では MessagePack を利用するという使い方が可能です。どちらも同じ API で透過的に扱うことができ、ファクトリメソッドを置き換えるだけで切り替えられます。


これは同時に、後述する 型変換機能や動的型付けオブジェクト、アノテーション機能 などの MessagePack の機能を、JSON でも利用できるということを意味します。実は JSON しか使わないというケースであっても、0.6 は非常に有用です。

Maven Central リポジトリ

Maven Central リポジトリからを MessagePack をダウンロードできるようになりました!
QuickStart for Java にあるように依存関係を記述すれば、自動的にインストールされます。

動的オブジェクトAPI

MessagePack for Java 0.6 では、新たに Value というクラスを提供します。
このクラスは 動的に型付けられたオブジェクト を表現するクラスで、RubyPython のような動的型付け言語のオブジェクトと同じように扱うことができます。もし必要であれば、前述の型変換 API も適用して 静的な型に変換することも可能 です。


本質的には、デシリアライズされたオブジェクトは静的には型が決定しておらず、デシリアライズされたタイミングで(=実行時に=動的に)型が決定します。それらの型を扱うプログラムを書くことで、ファイルフォーマットを完全にコントロールできるようになります。

Value クラスは↓こんな感じで利用できます:

MessagePack msgpack = new MessagePack();
Unpacker unpacker = msgpack.createStreamUnpacker(System.in);
for(Value v : unpacker) {
    if(v.isArrayValue()) { // 配列だったら...
        ArrayValue av = v.asArrayValue();
        List<Value> list = av;  // ArrayValue は List<Value> を implements している!
        Value e = list.get(0);
        ...
    }  else if(v.isIntegerValue()) {    // 整数だったら...
        IntegerValue iv = v.asIntegerValue();
        Number n = iv;    // IntegerValue は Number を implements している!
        short s = n.shortValue();
        ...
    }
    System.out.println(v);  // 値をダンプ
}

このプログラムで使用しているように、各 Value クラスは List や Number などのインタフェースを implements しています。使い込むほどに、Value クラスは非常に便利なことがお分かりいただけると思います。JRubyJava の連携にも有用です。


また非常に面白いのは Value の toString() はJSONを返す という点です。↓このようにデシリアライズしたオブジェクトを簡単にダンプして表示することができます:

Value v = ValueFactory.createArrayValue(
    new Value[] {
      ValueFactory.createIntegerValue(1),
      ValueFactory.createRawValue("ok?"),
    });

System.out.println(v);  //=> [1, "ok?"]

コードをデバッグをしていると、とりあえず値を表示させたいことがよくありますが、Valueクラスであれば System.out.println に渡すだけで値を表示できます。


さらに 0.6 では、 Value クラスは equals() と hashCode() を真面目に実装しています。
この挙動は MessagePack の型システムの意味論に基づいており、Ruby などの動的型付け言語との相互運用性を意識した挙動になっています。例えば、Java のShortとLongは同じ値でも hashCode が異なりますが、MessagePack ではどちらも 整数型 であり、同じ値であれば同じ hashCode を返します。

充実したテストケース

後述する Packer/Unpacker クラスや Value クラスを中心に、新しい機能が増えました。すると心配になるのは、その実装の品質ですね。
MessagePack for Java の開発では、品質向上に大きく注力しています。バージョン 0.6 は前バージョンのテストケースをすべてパスし、さらに大量のテストケースを追加しています。

ソースコードmsgpack/msgpack-java にあります。

Apache License 2.0

MessagePack for JavaJavassist というライブラリに依存していますが、ライセンス上の問題があって使えないという話が発生していました。
このたび、ライセンスの問題はきれいに解決しました。なんと MessagePack のために、Javassist のライセンスを変えていただきました^^; Javassist-3.15.0.GA は、MPL、LGPLApache License のトリプルライセンスでリリースされます。MessagePack for Java は、依存ライブラリを含めて Apache License の元で利用可能です。

型変換

MessagePack は、「自己記述的」なデータ形式です。シリアライズされたデータにオブジェクトの型情報も併せて埋め込むため、インタフェース定義ファイル(IDL)を別途用意する必要がありません。これは Protocol Buffers や Thrift とは異なり、MessagePack の大きな利点となっています。

ただし、受け取ったデータが正しい型であるかどうかチェックしたり、Listなどの静的な型に変換する仕組みは、依然として必要です。

MessagePack for Java は、この型変換の実装を強力にサポートする仕組みを備えています。0.6 では API を整理し、より使いやすくなっています。


まずシリアライズするには、単に Packerクラス の write メソッドにオブジェクトを渡して下さい:

import org.msgpack.MessagePack;
import org.msgpack.packer.BufferPacker;
public class Main {
    public static void main(String[] args) throws IOException {
        List<String> target = new ArrayList<String>();
        target.add("some");
        target.add("elements");

        // BufferPackerを作成
        MessagePack msgpack = new MessagePack();
        BufferPacker pk = msgpack.createBufferPacker();

        // シリアライズ
        pk.write(target);
        byte[] data = pk.toByteArray();
    }
}

↓こう書いてもOK:

import org.msgpack.MessagePack;
import org.msgpack.packer.BufferPacker;
public class Main {
    public static void main(String[] args) throws IOException {
        List<String> target = new ArrayList<String>();
        target.add("some");
        target.add("elements");

        // ちなみに MessagePack msgpack = new JSON();
        // と書くとシリアライズ結果がJSONになります
        MessagePack msgpack = new MessagePack();
        byte[] data = msgpack.write(target);
    }
}


このデータをデシリアライズするには、UnpackerクラスTemplatesクラス を使用します。↓このように "テンプレート" を渡します:

import org.msgpack.MessagePack;
import org.msgpack.unpacker.BufferUnpacker;
import static org.msgpack.template.Templates.tList;
import static org.msgpack.template.Templates.TString;
public class Main {
    public static void main(String[] args) throws IOException {
        // BufferUnpackerを作成
        MessagePack msgpack = new MessagePack();
        BufferUnpacker u = msgpack.createBufferUnpacker(data);

        // デシリアライズ
        List<String> deserialized = u.read(tList(TString));
    }
}


このように Templates を使って総称型にデシリアライズすることができます。

使いやすくなったアノテーション:Modelの実装を強力にサポート

JavaのコードでIDLを記述することができます。
@Optional@NotNullable@Ignore アノテーションをフィールドに付加することで、シリアライズ方法を制御することができます。


例えば、次のようなクラスを定義します:

public class User {
    public String name;
    public int age;
}

このUserクラスをシリアライズ可能にするには、単に @Message を付ければOKです:

import org.msgpack.annotation.Message;
@Message
public class User {
    public String name;
    public int age;
}


このクラスは 名前 と 年齢 のフィールドしか持っていませんが、新たに 性別 のフィールドを 後から追加しなければならない という話は、非常に良くあることです。このときに、既存のシステムやデータと 互換性を保ったまま 拡張しなければならないというケースもまた、良くあることです。
こうなると、途端に実装は複雑になってきます。この処理を自分で実装することを考えると…嫌気が差してきますね。まったくエキサイティングでない上に、バグが入りやすいコードです。


MessagePack では、そのあたりを自動的にうまく処理します。次のように単に String gender フィールドを追加して下さい:

import org.msgpack.annotation.Message;
@Message
public class User {
    public String name;
    public int age;
    public String gender;
}

もしフィールドが追加されていない古いデータを受け取ったときは、gender フィールドには null が入ります。
この挙動を許さずに、null が入らないようにチェックを自動的に行うには、↓このように @NotNullable アノテーションを追加してください:

import org.msgpack.annotation.Message;
import org.msgpack.annotation.NotNullable;
@Message
public class User {
    @NotNullable public String name;
    public int age;
    public String gender;
}

プリミティブ型はデフォルトで @NotNullable です。省略を許すには明示的に @Optional を指定するか、参照型を使って下さい。

その他の改善

テンプレートプリコンパイラ
実行時に Javassist を使ってシリアライザを生成・ロードする代わりに、実行前にコンパイルしておくことが可能。Android で MessagePack を使いたい場合に有効。
@Beansアノテーション
JavaBeans仕様を満たしたクラスをシリアライズ・デシリアライズ可能
バッファリングの最適化
バイト列へのシリアライズする際のバッファの管理方法を改善。シリアライズ速度が大幅に高速化。
ByteBuffer
ByteBufferからのデシリアライズをサポート。MappedByteBuffer から高速にデシリアライズが可能に。イベント駆動アプリケーションとの親和性も向上。
InputStreamとの統合
ストリームデシリアライザの内部バッファを廃止し、InputStream を直接利用するように変更。これにより MessagePack とそれ以外のデータ(ヘッダやメタデータなど)が入り交じったファイルフォーマットを扱うことが可能。
逆型変換
静的なオブジェクトから動的型付けオブジェクト(Value)への逆型変換をサポート。

リポジトリとドキュメント

MessagePack for Javaリポジトリは、msgpack/msgpack-java に移りました。
ドキュメントは MessagePack Wiki にあり、JavaDocmsgpack.org/javadoc/current にあります。
バグトラッカは jira.msgpack.org にあります。


MessagePack for Java 0.6 の実装は、私ふるはし(@frsyuki)と、西澤無我さん(@muga_nishizawa)によるものです。またAPIのレビューやテストや等々、色々な方々から数多くの協力を頂いています。ありがとうございます!
これからもよろしくお願いします^^;