MessagePack for Java 0.4 開発版リリース!

バイナリシリアライズ形式 MessagePackJava版の最新版をリリースしました!
新しいAPIを大量に追加し、使い勝手が大幅に向上しています。


今回は開発版のリリースで、安定版のリリースはもう少し先になります。
こういうAPIの方が良いのではないか、ここの実装にバグがある、このコードはもっと最適化できる、こんな用途に使ってみた などなど、フィードバックをお待ちしていますm(_ _)m


ここでは極めて重要な新機能である、

  • 動的コード生成
  • optionalフィールド
  • 動的型付けオブジェクト
  • テンプレート

について紹介します。

動的コード生成

リフレクションを使うと、クラスからメンバ変数の一覧を取得することができ、ユーザー定義のクラスをシリアライズ・デシリアライズできるようになります。これは非常に便利で、わざわざシリアライズ用のメソッドを自前で実装することなく、オブジェクトを渡すだけでシリアライズできるようになります。
しかし、リフレクションは遅いという重大な欠点があります。


そこで MessagePack では、リフレクションで取り出した情報を元にJavaのコードを生成し、それを実行時にコンパイル・リンクすることで、シリアライズ速度を大幅に高速化します。

使い方は↓このようになります。単にオブジェクトを渡すだけでシリアライズでき、クラスを指定するだけでデシリアライズできます:

import org.msgpack.MessagePack;
import org.msgpack.annotation.MessagePackMessage;

public class Main {
    // ユーザー定義のクラス
    // アノテーションを付ける
    @MessagePackMessage
    public static class MyClass {
        public String str;
        public double num;
    }

    public static void main(String[] args) {
        MyClass src = new MyClass();
        src.str = "msgpack";
        src.num = 0.4;

        // シリアライズ
        byte[] raw = MessagePack.pack(src);

        // デシリアライズ
        MyClass dst = MessagePack.unpack(raw, MyClass.class);
    }
}

とても簡単ですね! 革命的な使いやすさです。
このようにアノテーションを付けるだけで、高速にシリアライズ・デシリアライズができます。


何かの事情でアノテーションを付けられない場合でも、事前に登録しておけば同じ効果が得られます。

        // ユーザー定義クラスを事前に登録しておく
        MessagePack.register(MyClass.class);


動的コード生成機能の実装は、分散Key-valueストア「ROMA」の開発者としても知られる @muga_nishizawaさんによるものです。実はJavaで定番の動的コード生成ライブラリであるJavassistの開発者でもあったようです。

optionalフィールド

データの寿命は、プログラムの寿命より長いことがあります。
このためデータとプログラムはできるだけ分離しておくのがセオリーとされますが、ここでデータとプログラムの間に立つシリアライザは、悩ましい問題を抱えることになります。プログラムが変更され、データのフォーマットが変更されたときでも、残されている古いデータも正しく扱えなければなりません。


この問題に対して MessagePack では、オブジェクトをシリアライズシリアライズしているとき、互換性を保ったままメンバフィールドを追加できるようにする仕組みを提供します。

まず↓このようなクラスがあったとします:

@MessagePackMessage
public static class MyClass {
    public String str;
    public double num;
}

この定義に従ってシリアライズされたデータがたくさん蓄積されていたり、この定義に従って動いている再起動したくないサーバがあるとします。
これらとの互換性を保ったまま、クラスの定義を↓このように変更することができます:

@MessagePackMessage
public static class MyClass {
    public String str;
    public double num;

    // 新しいフィールドをオプショナルとして追加
    @MessagePackOptional
    public int flag = 0;
}

新しく追加したフィールドにはアノテーションを付け、optionalなフィールドにします。


この定義に従って古いデータを読み込むと、optionalなフィールドにはデフォルト値が設定されます。
逆に、新しいフィールドが追加されているデータを古いコードで読み込むこともできます。その場合は新しいフィールドは単に無視されます。これによって古いサーバと新しいサーバが相互に通信することができます。ローリングアップデートができますね。

動的型付けオブジェクト

MessagePack は、シリアライズ後のバイト列にオブジェクトの「型」を保存します。このバイト列をデシリアライズする側から見ると、デシリアライズ時=実行時に型が決定される動的型付けの性質を持っていると言えます。
例えば Ruby は動的型付け言語なので、このデータそのまま扱えるのですが、Java は静的型付け言語なので、そのままではうまく扱えません。具体的に言えば、デシリアライズされたオブジェクトの型が Object だと非常に使いにくいです。


そこで MessagePack では、動的型付けオブジェクトテンプレート という2つの機能を提供します。


まず MessagePackObject は、動的に型付けされたオブジェクトを表現するクラスです。
MessagePack.unpack(byte[] buffer) などのデシリアライズAPIの返り値は、このMessagePackObjectになります。このクラスは、実際の型を検査するメソッド(isIntegerType()isListType()など)や、静的な型に変換するメソッド(asString()convert(Template)など)を持っています。

MessagePackObject を使うことで、コンパイル時には知り得ない未知のデータ をうまく取り扱うことができます。

テンプレート

テンプレートは、デシリアライズされたオブジェクトを静的な型に変換する機能を提供します。同時に型チェックを行い、想定外のデータだったら例外(MessageTypeException)を投げます。
テンプレートは↓このように使います:

import java.util.ArrayList;
import java.util.List;

import org.msgpack.MessagePack;
import static org.msgpack.Templates.*;

public class Main {
    public static void main(String[] args) {
        // シリアライズするオブジェクトを作成
        List<String> src = new ArrayList();
        src.add("msgpack");
        src.add("kumofs");
        src.add("viver");

        // バイト列にシリアライズ
        byte[] raw = MessagePack.pack(src);

        // 方法1: テンプレートを指定して直接デシリアライズ
        List<String> dst1 = (List<String>)MessagePack.unpack(raw, tList(TString));

        // 方法2: いったん動的型付けオブジェクトにデシリアライズしてから型変換
        MessagePackObject dynamic = MessagePack.unpack(raw);
        List<String> dst2 = (List<String>)dynamic.convert(tList(TString));
    }
}

テンプレートは org.msgpack.Templates.* に定義されており、import static すると tList(Template elementTemplate)tArray(Template elementTemplate)TStringTIntegerなどのテンプレートが使えるようになります。

使い方

多くの機能は org.msgpack.MessagePack クラスで使えるようになっています:

package org.msgpack;
public class MessagePack {
    public static byte[] pack(Object obj);
    public static byte[] pack(Object obj, Template tmpl) throws MessageTypeException;
    public static void pack(OutputStream out, Object obj) throws IOException;
    public static void pack(OutputStream out, Object obj, Template tmpl) throws IOException;

    public static MessagePackObject unpack(byte[] buffer) throws MessageTypeException;
    public static MessagePackObject unpack(InputStream in) throws IOException;
    public static Object unpack(byte[] buffer, Template tmpl) throws MessageTypeException;
    public static Object unpack(InputStream in, Template tmpl) throws IOException, MessageTypeException;
    public static <T> T unpack(byte[] buffer, Class<T> klass) throws MessageTypeException;
    public static <T> T unpack(InputStream in, Class<T> klass) throws IOException, MessageTypeException;

    public static void register(Class<?> target);
    public static void register(Class<?> target, Template tmpl);
    public static void registerPacker(Class<?> target, MessagePacker packer);
    public static void registerConverter(Class<?> target, MessageConverter converter);
    public static void registerUnpacker(Class<?> target, MessageUnpacker unpacker);
}

インストール

MessagePack for Java 0.4-devel は、mavenを使ってインストールできます。
mavenリポジトリhttp://msgpack.org/maven2/org/msgpack/msgpack/ にあります。*1
ソースコードhttp://github.com/msgpack/msgpack にあります。


フィードバックはTwitterでお気軽に!

現在のJava版のメンテナは@muga_nishizawa@frsyukiで捕捉できます。

メーリングリストとIRCもあります。ぜひどうぞ。MessagePack Users


The MessagePack Project

*1:javassistのバージョン3.12.1.GA以降に依存しています。javassistは[https://repository.jboss.org/nexus/content/groups/public/:title=JBossのmavenリポジトリ]にあります。