先々週、先週とjava.util.ResourceBundleクラスについて解説してきました。今週はその最終回です。

今週はリソースバンドルのキャッシュと、リソースバンドルの形式について解説していきます。まずはキャッシュからいきましょう。

リソースバンドルのキャッシュ制御

リソースバンドルはResourceBundle.getBundleメソッドによって取得することができます。

getBundleメソッドではデフォルトでResourceBundleオブジェクトをキャッシュします。再び同じファミリ名でgetBundleがコールされた場合、キャッシュしているResourceBundleオブジェクトを返します。

キャッシュをすることでリソースバンドルの読み込みが2回目からは迅速に行えるという利点があります。その一方、リソースバンドルを本当に読み込みなおしたいときでも、キャッシュがあるため再読み込みができないという不都合もあります。

この不都合も先週紹介したResourceBundle.Controlクラスを使用することで解決できます。

ResourceBundle.Controlクラスでは、リソースバンドルのキャッシュの有効期限を表すためにgetTimeToLiveメソッドが定義されています。このメソッドの戻り値はlongで、リソースバンドルの有効期限をミリ秒で示しています。

デフォルトでは有効期限なしとなっていますが、メソッドをオーバライドすることで任意の時間に設定することができます。

ResourceBundleCacheSample
図1 ResourceBundleCacheSample

このサンプルを実行すると、図1に示したようにフレームにボタンが表示されます。ボタンをクリックするとリソースバンドルの再読み込みが行われます。

再読み込みが本当に行われたかを調べるために、2つのプロパティファイルを用意しました。一方がデフォルトプロパティファイル、もう一方が日本語ロケールに対応したプロパティファイルです。

サンプルの実行時にはデフォルトプロパティファイルだけを配置しておき、実行後日本語ロケールのプロパティファイルを配置します。

リソースバンドルの再読み込みが行われれば、デフォルトより先に日本語ロケールのリソースバンドルが使用されるはずです。

public class ResourceBundleCacheSample {
    private ResourceBundle.Control control;
 
    public ResourceBundleCacheSample() {
        // デフォルトのResourceBundle.Controlオブジェクト
        control = new ResourceBundle.Control() {};
 
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("ResourceBundle Sample");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
                ResourceBundle bundle
                    = ResourceBundle.getBundle(
                        "net.javainthebox.resources", control);
 
                JButton button = new JButton(
                                     bundle.getString("button.name"));
                button.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        JButton button = (JButton)event.getSource();

                        // リソースバンドルの再読み込み
                        ResourceBundle bundle
                            = ResourceBundle.getBundle(
                                "net.javainthebox.resources", control);
                        button.setText(bundle.getString("button.name"));
                    }
                });
                frame.add(button);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    public static void main(String[] args) {
        new ResourceBundleCacheSample();
    }
}

ボタンがクリックされると赤字で示したように、リソースバンドルの読み込みを行います。

プロパティファイルは次の2つです。まずはデフォルトのresources.propertiesファイル。

button.name=Reload

次が日本語ロケールのresources_ja.propetiesファイルです。

button.name=\u518d\u8aad\u307f\u8fbc\u307f

\u518d\u8aad\u307f\u8fbc\u307fは「再読み込み」をnative2asciiコマンドで変換したものです。

さて、はじめはデフォルトのキャッシュ制御を行わないResourceBundle.Controlクラスを使ってみましょう(青字)。何も行わないのであれば、getBundleメソッドの引数にする意味はないのですが、まぁそこは実験、実験。

まずは.\net\javaintheboxディレクトリにはresources.propertiesファイルだけを置いておきます。

そして、ResourceBundleCacheSampleクラスを実行します。当然のことながら、ボタンにはReloadと表示されます(図1)。

次にresources_ja.propertiesファイルを.\net\javaintheboxディレクトリに配置します。そして、Reloadボタンをクリックすると... 何も変わりません。

この結果は、キャッシュが使われており、再読み込みが行われていないことを示しています。

次に上記の青字の部分を以下のように変更してみました。

        control = new ResourceBundle.Control() {
            public long getTimeToLive(String baseName,
                                      Locale locale) {
                // リソースバンドルのキャッシュの有効期限は10秒
                return 10000L;
            }
        };
リソース再読み込み後の結果
図2 リソース再読み込み後の結果

有効期限を返すgetTimeToLiveメソッドをオーバライドし、10秒(=10000ミリ秒)を返すようにしています。

これで先ほどと同じようにResourceBundleCacheSampleを実行します。

起動してから10秒以内であれば、resources_ja.propertiesファイルを配置したとしても再読み込みは行われません。しかし、10秒過ぎるとキャッシュが無効になるため、再読み込みが行われます。その結果、図2に示したように「再読み込み」と表示されます。

getTimeToLiveメソッドの引数はリソースバンドルのベース名(ファミリ名とパッケージをあわせたもの)とロケールなので、ベース名やロケールに応じて有効期限を変化させることも可能です。

また、ResourceBundle.Controlクラスでは、有効期限を決めるための2つの定数が定義されています(表1)。これらの定数を使えば、あるベース名のリソースバンドルは常にキャッシュせず、別のリソースバンドルは有効期限がある、などというような有効期限の制御を行うことができます。

表1 有効期限設定のための定数
定数名 説明
TTL_DONT_CACHE リソースバンドルをキャッシュしない
TTL_NO_EXPIRATION_CONTROL 有効期限がなく、常にキャッシュを使用する

なお、プログラム中で明示的にキャッシュをクリアするために、ResourceBundleクラスにclearCacheメソッドも新たに定義されました。

リソースバンドルの形式

デフォルトのリソースバンドルの形式は先々週に解説したように、クラスファイルとプロパティファイルの2種類です。それぞれ一長一短がありますが、手軽にリソースバンドルを使うにはプロパティファイルの方が適しています。

ところが、プロパティファイルはUnicodeエスケープで表記しなければならないという欠点があります。英語であればいいのですが、日本語ではnative2asciiコマンドを使用して変換する必要があります。

また、Unicodeエスケープで記述されているためすぐに内容を確認することができません。

プロパティファイルの読み書きを行うPropertiesクラスは、J2SE 5.0からXMLで表記したプロパティファイルを扱うことができるようになりました。XMLであれば任意の文字コードを使用することができます。

ところが、リソースバンドルは未だにXMLを扱うことができません。

もちろん、ResourceBundleクラスを派生させて、XMLファイルを使用するリソースバンドルを作成することは可能です。しかし、他の形式のリソースバンドルと自作のリソースバンドルを一緒に扱うことはできません。

そこで、ResourceBundle.Controlクラスの登場です。ResourceBundle.Controlクラスを使うことで任意の形式のリソースバンドルを組み込むことができるようになったのです。

サンプルはもちろんXMLファイルを扱うリソースバンドルです。

サンプルは3つのクラスから構成されています。XMLファイルからリソースバンドルを読み込むXMLResourceBundleクラス、ResourceBundle.getBundleメソッドでXMLResourceBundleクラスを読み込ませるためのXMLResourceBundleControlクラス、そして実際にリソースバンドルを使用するXMLResourceBundleSampleクラスです。