How to use maru-dog JavaBeansプロパティデータバインドライブラリ (7)

前回からかなり時間が空きましたが今回はバインド先とバインド元のJavaBeansに定義されたバインドアノテーションを上書きし、バインドの振舞いを変える方法を紹介します。

maru-dogはアノテーションで付与されたバインド情報を上書きし、振舞いを変更するために内部DSLを提供しています。アプリケーション実行時に、例えば、あるコンディションによってバインド処理を若干変えたい場合などに利用することができます。

まず、通常のバインド処理の例を示します。

import org.maru.dog.Director;
import org.maru.dog.Marudog;
import org.maru.dog.annotation.Bind;
import org.maru.dog.annotation.Bound;
import org.maru.dog.annotation.From;
import org.maru.dog.converter.Converter;

public class Main {

    public static void main(String[] args) {
        Director director = Marudog.getDirector();
        UserBean userBean = director.getInstance(UserBean.class, getUserMaster(), getUserProfile());
        System.out.println(userBean);
    }

    private static UserMaster getUserMaster() {
        UserMaster master = new UserMaster();
        master.userId = "ABC123";
        master.password = "PASSWORD";
        master.name = "user name";
        return master;
    }

    private static UserProfile getUserProfile() {
        UserProfile profile = new UserProfile();
        profile.address = "Tokyo/Japan";
        profile.age = 25;
        profile.gender = 1;
        profile.userName = "Maru dog";
        return profile;
    }

    static class UserBean {
        @Bound
        private String userId;

        @Bound
        @From(UserMaster.class)
        private String name;

        @Bound
        private String address;

        @Bound
        private short age;

        @Bound
        private String gender;

        @Override
        public String toString() {
            return "UserBean [userId=" + userId + ", name=" + name
                    + ", address=" + address + ", age=" + age + ", gender="
                    + gender + "]";
        }

    }

    static class UserMaster{
        @Bind
        private String userId;

        @Bind
        private String name;

        private String password;
    }

    static class UserProfile {
        @Bind
        private String userName;

        @Bind
        private String address;

        @Bind
        private short age;

        // 1:man 2:woman
        @Bind
        @Converter(converterClass = GenderConverter.class)
        private byte gender;
    }

    static public class GenderConverter {
        public GenderConverter(){}

        public String execute(byte gender) {
            if (gender == 1) {
                return "man";
            } else if (gender == 2){
                return "woman";
            } else {
                throw new IllegalArgumentException("Unknown gender.");
            }
        }
    }
}

このサンプルプログラムに関して、これまでの日記を読んでいる方には解説不要かと思います。
実行結果以下のようになります。

UserBean [userId=ABC123, name=user name, address=Tokyo/Japan, age=25, gender=man]

それでは、maru-dogが提供する内部DSLを用いてバインド定義を上書きし、制御を変えてみます。

これを行うにはDrectorオブジェクト生成時にパラメータにConfigクラスの実装を渡してやる必要があります。Configクラスは抽象クラスで、defineという抽象メソッドを持っているので、バインド定義を上書きする際には具象クラスでこのメソッド内に制御を定義する必要があります。

import org.maru.dog.Config;
import org.maru.dog.Director;
import org.maru.dog.Marudog;
import org.maru.dog.annotation.Bind;
import org.maru.dog.annotation.Bound;
import org.maru.dog.annotation.From;
import org.maru.dog.converter.Converter;

public class Main {

    public static void main(String[] args) {
        Director director = Marudog.getDirector(new Config() {

            @Override
            protected void define() {
                declareFor(UserBean.class);
                    bound("name").from(UserProfile.class).bind("userName");
                end();
            }

        });
        UserBean userBean = director.getInstance(UserBean.class, getUserMaster(), getUserProfile());
        System.out.println(userBean);

    }

    private static UserMaster getUserMaster() {
        UserMaster master = new UserMaster();
        master.userId = "ABC123";
        master.password = "PASSWORD";
        master.name = "user name";
        return master;
    }

    private static UserProfile getUserProfile() {
        UserProfile profile = new UserProfile();
        profile.address = "Tokyo/Japan";
        profile.age = 25;
        profile.gender = 1;
        profile.userName = "Maru dog";
        return profile;
    }

    static class UserBean {
        @Bound
        private String userId;

        @Bound
        @From(UserMaster.class)
        private String name;

        @Bound
        private String address;

        @Bound
        private short age;

        @Bound
        private String gender;

        @Override
        public String toString() {
            return "UserBean [userId=" + userId + ", name=" + name
                    + ", address=" + address + ", age=" + age + ", gender="
                    + gender + "]";
        }

    }

    static class UserMaster{
        @Bind
        private String userId;

        @Bind
        private String name;

        private String password;
    }

    static class UserProfile {
        @Bind
        private String userName;

        @Bind
        private String address;

        @Bind
        private short age;

        // 1:man 2:woman
        @Bind
        @Converter(converterClass = GenderConverter.class)
        private byte gender;
    }

    static public class GenderConverter {
        public GenderConverter(){}

        public String execute(byte gender) {
            if (gender == 1) {
                return "man";
            } else if (gender == 2){
                return "woman";
            } else {
                throw new IllegalArgumentException("Unknown gender.");
            }
        }
    }
}

上記プログラムの実行結果は次のようになります。

UserBean [userId=ABC123, name=Maru dog, address=Tokyo/Japan, age=25, gender=man]

結果を比較するとUserBeanのnameプロパティが"Maru dog"となっています。

プログラムを順を追って説明します。まずDirectorオブジェクトを取得する際にMarudog.getDirectorメソッドにConfigクラスを継承した無名クラスを渡しています。上記例では無名クラスですが、別途特定のクラスとして実装してもかまいません。

ここで重要なことはConfigクラスを継承し、defineメソッド内にバインドの振舞いを変える定義をすることです。

ではdefineメソッドのバインド定義について説明します。

defineメソッドの中では必ずバインド先となるクラス単位で定義を行う必要があります。declareForでバインド先のBeanクラスを指定し、その定義を終える場合は必ず最後にendメソッドで定義を閉じなければなりません。

declareForとendの間に実際のバインド制御を定義していきます。

bound()でバインド先のプロパティを指定し、from()でバインド元のクラスを、bind()でバインドものとプロパティ指定します。この1流れをdeclareFor〜end間で複数定義することが可能です。

例:declareFor〜endの定義例

Director director = Marudog.getDirector(new Config() {

    @Override
    protected void define() {
        declareFor(TargetBean.class);
            bound("target1").from(FromBean1.class).bind("from1");
            bound("target2").from(FromBean1.class).bind("from2");
            bound("target3").from(FromBean2.class).bind("from3");
        end();
    }
});

上記サンプルプログラムの例に話を戻すと、UserBeanクラスのバインド先のnameと名付けられたプロパティに対してUserProfileクラスのuserNameと名付けられたプロパティの値をバインドせよという意味になります。

その結果、アノテーションでBeanに直接定義したバインド情報を上書きしUserProfileの"userName"がバインドされています。

今回はここまでです。次回以降もう少し詳細にmaru-dogが提供するDSLについて説明したいと思います。

プロジェクトトップ:http://maru.sourceforge.jp/index.html
ドキュメントサイト:http://maru.sourceforge.jp/document_dog.html
ダウンロードサイト:http://sourceforge.jp/projects/maru/releases/