Spring Framework 入門記 Contextその2 MessageSource

Contextパッケージの2回目です.前回BeanFactoryの代わりにApplicationContextを使いました.今回は,BeanFactoryにはない,ApplicationContext特有の機能を使ってみます.
まずは「3.10.1. Using the MessageSource」です.
ApplicationContextは,MessageSourceインタフェースもextendsしています.MessageSourceは,国際化(i18n)対応のメッセージを提供するためのインタフェースらしいです.苦手なんですよね,国際化.いやその,日本語化も苦手なんですけどね...
それでその,MessageSourceには次の3つのメソッドがあります.

  • getMessage(String code, Object[] args, Locale loc)
  • getMessage(String code, Object[] args, String default, Locale loc)
  • getMessage(MessageSourceResolvable resolvable, Locale locale)

最初の2つのメソッドの第1引数codeはリソースバンドルで言うところのキーで,java.text.MessageFormatで扱うパターン(フォーマット)文字列を得るために使われます.第2引数argsはそのMessageFormatに渡される引数ですね.
3番目のメソッドのMessageSourceResolvableは,2番目のメソッドの引数であるcodeargsdefaultを取得するgetterをメソッドして持つインタフェースです.つまり,このインタフェースをimplementsしたクラスは,ごく普通のBeanとして扱えるようなクラスです.こちらを使うと,メッセージに含める引数などもBean定義XMLファイルで様々に定義できるため,より柔軟性を高めることが出来るということでしょう.
さて,ApplicationContextMessageSourceimplementsしているわけですが,だからといってFileSystemXmlApplicationContext*1は直接MessageSourceの機能を実装しているわけではないようです.本物のMessageSource実装クラスに委譲するだけなんですね.ではその本物の実装は? というと,StaticMessageSourceResourceBundleMessageSourceの2つが用意されています.このうち前者はプログラマティック*2にメッセージを設定するというもので,あまりうれしくない実装です.一方後者はおなじみのリソースバンドルを使うタイプです.うん,この方が素敵(キラッ).
ちょっと意外だったのが,ApplicationContextMessageSourceの実装を入手する手段です.どうやら,messageSourceという名前のBeanが定義されていれば,それを使ってくれるのだとか.ふーん,コンテナが予約しているも同然の名前があるってことですね.ちなみにmessageSourceという名前のBeanが見つからない場合はApplicationContextが空のStaticMessageSourceを作成します.
国際化に対応したアプリケーションを作るなら,こういうものは必須なのでしょうね.国際化するわけではなくても,エラーメッセージやロギングのメッセージがそこら中のソースに散らばってしまうと大変です.でもこれ,ApplicationContextの機能なんですよね.ということは,これを使うとそのクラスはApplicationContextに依存してしまいます.うーん,あちら立てればこちらたたず.今回はあくまでもApplicationContextの学習がメインなので,その問題には触れずにいくことにしましょう.えっ? 逃げてるわけじゃありませんよっ(ドキドキ).
とにもかくにもですね,この機能を使うにはApplicationContextを入手しなければいけません.大体予想がつきますね.一貫性っていいなぁ.そうです,ApplicationContextAwareインタフェースをimplementsすればいいのです.BeanFactoryを入手するのにBeanFactoryAwareを使ったのと同じですね.
ということで,例によって実験コーナーの始まりです.
せっかくなので(何が?),MessageSource第3のメソッドを使ってみることにします.
まずは,MessageSourceResolvableimplementsした単純なクラスを用意します.

package study;
import org.springframework.context.MessageSourceResolvable;

public class Headline implements MessageSourceResolvable {
    private Object arguments;
    private String codes;
    private String defaultMessage;

    public Object getArguments() {
        return arguments;
    }
    public void setArguments(Object objects) {
        arguments = objects;
    }
    public String getCodes() {
        return codes;
    }
    public void setCodes(String strings) {
        codes = strings;
    }
    public String getDefaultMessage() {
        return defaultMessage;
    }
    public void setDefaultMessage(String string) {
        defaultMessage = string;
    }
}

これで,様々なメッセージフォーマットのキーとそのパラメータをBeanとして扱うことが出来ます.
次に,このHeadlineをプロパティとして持ち,ApplicationContextにフォーマットしてもらって出力するdisplay()メソッドを持つクラスを作ります.

package study;
import java.util.Locale;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSourceResolvable;

public class Billboard implements ApplicationContextAware {
    private ApplicationContext context;
    private MessageSourceResolvable headline;

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }
    public MessageSourceResolvable getHeadline() {
        return headline;
    }
    public void setHeadline(MessageSourceResolvable resolvable) {
        headline = resolvable;
    }
    public void display() {
        System.out.println(context.getMessage(headline, Locale.getDefault()));
    }
}

次に,これらのクラスを定義したXMLファイルを作成します(<bean>要素のみ抜粋).

    <bean name="headline" class="study.Headline">
        <property name="codes"><value>headline</value></property>
        <property name="defaultMessage"><value>Spring is commig.</value></property>
    </bean>
    <bean name="billboard" class="study.Billboard">
        <property name="headline"><ref bean="headline"/></property>
    </bean>

次に実行用のクラス.いつものとあまり変わりません.

package study;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {
    public static void main(String args) {
        try {
            FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                    new String { "factory.xml", "beans.xml" });

            Billboard billboard = (Billboard) context.getBean("billboard");
            billboard.display();

            context.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

これを実行すると,次のようになります.

Spring is commig.

おかしいですねぇ,もう春は来てるのに.
... すみません,わざとです.ごめんなさい.
これは,messageSourceというBeanを定義していないため,空のStaticMessageSourceが使われ,そこにはメッセージはまったく登録されていないのでデフォルトのメッセージが出力されたわけですね.
ということで,ResourceBundleMessageSourceをBean定義XMLに組み込みましょう(<bean>要素のみ抜粋).

    <bean name="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource"
    >
        <property name="basenames">
            <list>
                <value>headline</value>
            </list>
        </property>
    </bean>

ResourceBundleMessageSourceは,リソースバンドルのベース名のリストを受け取るのですが,ここでは一つ(headline)だけ指定しています.
そしてリソースバンドルを日本語用と英語用にそれぞれ作成します.
まず日本語用(headline_ja.properties).native2ascii済みです.

headline=\u6625\u304c\u6765\u305f\uff0e

それから英語用(headline_en.properties).

headline=Spring is here.

これを普通に(ロケールを指定しないで)実行すると,次のようになります(日本語環境だもん).

春が来た.

来てます!
次に,-Duser.language=en -Duser.country=USを付けて実行すると,次のようになります.

Spring is here.

いいねぇ!

*1:クラス名も長ければいいって訳じゃないですよね

*2:前回からのお気に入りフレーズ