Springを利用した アプリケーションで、設定ファイルを外出しする簡単な方法

Spring Framework を使っているアプリケーションで、設定ファイルを外部から読む簡単な方法を考えてみる(簡単なだけであって、ベストプラクティスかどうかは不明)。

アプリケーションの設定を保持するクラスは以下のような感じとする*1

package example;

public class Settings {
    private int foo;
    private String bar;

    public int getFoo() {
        return foo;
    }

    public void setFoo(int foo) {
        this.foo = foo;
    }

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        this.bar = bar;
    }
}

Springのbeans設定ファイルを直接使用する方法

アプリケーション埋め込みの設定ファイルでデフォルト値を設定する。ここで、デフォルト値を設定する bean と、実際に「設定」として使用する bean を分けていることに注意。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
   
  <bean id="defaultApplicationSettings" abstract="true" class="example.Settings">
    <property name="foo" value="10" />
    <property name="bar" value="こんにちは" />
  </bean>

  <!-- この段階では、デフォルト設定をそのまま使用することになる -->
  <bean id="applicationSettings" parent="defaultApplicationSettings">
  </bean>

</beans>

上記のファイルに対応して、デフォルト設定の上書きを可能にするためのユーザ設定用のファイルを以下のように作成する。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
       
  <!-- デフォルトを上書きしたい設定のみ記述 -->
  <bean id="applicationSettings" parent="defaultApplicationSettings">
    <property name="bar" value="こんばんわ" />
  </bean>

</beans>

あとは、前者のファイルの後に、後者のファイルが読まれるようにSpringに指示すると、ユーザ設定がデフォルト設定を上書きした設定beanが "applicationSettings" という名前で得られるので、これを他のオブジェクトにインジェクションして使えば良い。

例えば、Webアプリケーションなら web.xml で以下のように指定してから、前者のファイルを applicationContext.xml としてアプリケーションに埋め込み、後者のファイルは ${HOME}/.my-app/settings.xml にでも配置して、${HOME} をTomcat等のクラスパスに追加する、といったやり方が考えられる。

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:applicationContext.xml
      classpath*:.my-app/settings.xml
    </param-value>
  </context-param>

  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

classpath* という指定なのはファイルが無くてもエラーにしないためで、これを classpath: にするとファイルが無い場合エラーになる*2

ただし、この方法だとユーザ設定の編集者が自由にSpringの設定をいじれることになるので、設定者が信頼できない場合はお勧めできない。

JavaのPropertiesを使用する方法

もう一つの方法がこちら。

まず、アプリケーション埋め込みのプロパティファイルでデフォルト値を設定する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
  <comment>Default settings</comment>
  <entry key="foo">10</entry>
  <entry key="bar">こんにちは</entry>
</properties>

また、上記のファイルに対応して、デフォルト設定の上書きを可能にするためのユーザ設定用のファイルを以下のように作成する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
  <comment>Custom settings</comment>
  <!-- デフォルトを上書きするキーのみ指定 -->
  <entry key="bar">こんばんわ</entry>
</properties>

設定はJava 5 から使用できるようになったXMLプロパティファイルを使用する*3。デフォルト設定の方は開発者が作成するので properties ファイルでも構わないが、ユーザ設定はJavaの知識の無い人もいじる可能性があるので、日本語を設定したりするときに native2ascii を強要するというのはまずい。

あとは、前者のファイル→後者のファイルの順に locations を指定した PropertyPlaceholderConfigurer の bean を登録し、プレースホルダを使ってアプリケーション設定 bean を構成すれば良い。

(2009/07/18:クラスパスを追加しなくても良いようなので修正)例えば、Springのbeans設定ファイルに以下のような設定を行ってから、前者のファイルを default-settings.xml としてアプリケーションに埋め込み、後者のファイルは任意設定として ${HOME}/.my-app/settings.xml に配置する、といったやり方が考えられる。

  <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:default-settings.xml</value>
        <value>file:${user.home}/.my-app/settings.xml</value>
      </list>
    </property>
    <property name="ignoreResourceNotFound" value="true" />
  </bean>

  <bean id="applicationSettings" class="example.Settings">
    <property name="foo" value="${foo}" />
    <property name="bar" value="${bar}" />
  </bean>

settings.xml の方を任意設定にするため、ignoreResourceNotFound を設定している。

こちらの方法はユーザ設定の編集者が信頼できない場合にも(たぶん)安全で、プロパティ名を間違ってもデフォルト値が使われるだけで致命的な問題にならないなど、最初の方法より良い気がする。

*1:immutableでないのは場合によっては問題になるかも。面倒なのでここではそこまで対処しない。Rubyのfreezeのような簡単な方法があればいいんだけど。

*2:この指定はテスト用の設定ファイルを指定するのにも使用できる。Maven2等でテスト時のみ applicationContext-test.xml がクラスパス上に存在するよう構成し、classpath*:applicationContext-test.xml と指定するとか

*3:SpringのPropertyPlaceholderConfigurerの元になっているPropertiesLoaderSupportXMLのプロパティファイルをきちんと読んでくれてナイス()