Java
spring
spring-boot

Spring Bootの外部設定値の扱い方を理解する

今回は外部設定値(プロパティファイル、JVMのシステムプロパティ、環境変数などに定義した設定値)をSpring Bootがどのように扱うのか紹介したいと思います。
なお、前回紹介した「Spring BootのAutoConfigureの仕組みを理解する」でも外部設定値を参照した条件付きBean定義の仕組みがサポートされていたり、各AutoConfigure用のコンフィギュレーションクラスの中から外部設定値を参照してBean定義していたりします!!

前提バージョン

  • Spring Boot 1.4.1.BUILD-SNAPSHOT (投稿時点のスナップショット)
  • Spring Framework 4.3.3.BUILD-SNAPSHOT (投稿時点のスナップショット)

外部設定として扱う値は?

アプリケーションを作成する場合、ソースコード上にハードコーディングできない(or したくない)値を扱うことがよくあります。具体的には・・・以下のような値が外部設定値として扱う候補になります。

  • データベースへの接続情報などの環境に依存する値
  • 機能要件で決められたアプリケーションの動作を変更するためのパラメータ値
  • 機能要件上はパラメータ化する必要はないけどテスト容易性などを考慮して外部化しておいた方がよい値
  • 性能要件に応じてチューニングが必要になる事が想定されるパラメータ値
  • etc..

外部設定値の取得方法

Spring Bootアプリケーションでは、以下のいずれかの方法で外部設定値を取得することができます。

  • Spring Frameworkが提供している@Valueを使用する
  • Spring Bootが提供している「Type-safe Configuration Properties」の仕組みを利用する (この仕組みを使うのがお勧めです :laughing: :laughing: :laughing:
  • Spring Frameworkが提供しているEnvironmentインタフェースを利用する。

それぞれ簡単に取得方法を紹介しておきましょう。

@Valueの利用

DIコンテナで管理するクラスのフィールド、setterメソッド、コンストラクタ引数などに@Value(${設定名:デフォルト値})の形式で指定すると、指定した設定値がインジェクションされます。デフォルト値は省略可能ですが、デフォルト値が未指定の状態で設定値の指定がないとエラーになります。

@Component
public class AppProperties {

    @Value("${app.timezone:UTC}") 
    private TimeZone timezone; // ← "app.timezone"の設定値がインジェクションされる

    // ... getter/setter
}

Type-safe Configuration Propertiesの利用

DIコンテナで管理するクラスに@ConfigurationPropertiesを付与すると、クラス内のプロパティに設定値(「prefix属性に指定したプレフィック + "." + プロパティ名」の値)が自動的にインジェクションされます。デフォルト値を設けたい場合は、プロパティの初期値として指定してください。

@Component
@ConfigurationProperties(prefix="app")
static class AppProperties {

    private TimeZone timezone = TimeZone.getTimeZone("UTC"); // ← "app.timezone"の設定値がインジェクションされる

    // ... getter/setter

}

この方法は、Spring BootのAutoConfigureの中でも利用されています。なお、「Type-safe Configuration Properties」の仕組みについては、本投稿とは別の投稿で詳しく紹介したいと思います。

Environmentインタフェースの利用

外部設定値は、Spring Frameworkが提供しているEnvironmentインタフェースのメソッドを介して取得することもできます。プログラム内で動的に取得先(設定名)を切り替えるような使い方をしたい場合は、この方法を利用を検討してみてください。

@Service
public class MyService {
    private static final TimeZone DEFAULT_TIMEZONE= TimeZone.getTimeZone("UTC");
    private final Environment environment;
    public MyService(Environment environment) { // Environmentをインジェクション
        this.environment = environment;
    }
    public void printApplicationManagedTimezone() {
        TimeZone timezone = environment.getProperty("app.timezone", TimeZone.class, DEFAULT_TIMEZONE);
        System.out.println(timezone);
    }

}

外部設定値の指定方法

以下に、外部設定値の主な指定方法を(優先度が高い順に)紹介します。なお、個人的にたぶん利用しないだろうな〜と思った方法については説明は割愛します。(サポートしている指定方法の全量は、Spring Bootのリファレンスをご覧ください)

指定方法 説明
Developer tools用のプロパティファイル Spring Boot Developer tools用のプロパティファイル(~/.spring-boot-devtools.properties)に「設定名=値」の形式で指定する。(Spring Boot Developer toolsを適用している場合のみ)
@SpringBootTest @SpringBootTestproperties属性に「設定名=値」の形式で指定する。テストケース内で設定値を指定したい場合に利用。 (ただし、後述の@TestPropertySourceproperties属性に同じ設定値があると、@TestPropertySourceの値が有効となる。ってかそんな使い方しないと思いますが・・・ :sweat_smile:
@TestPropertySource @TestPropertySource(テスト用のプロパティファイル or アノテーションの属性値)に「設定名=値」の形式で指定する。テストケース内で設定値を指定したい場合に利用。
コマンドライン引数 コマンドライン引数に「--設定名=値」の形式で指定する。
JNDIリソース java:comp/env/設定名」の形式で取得可能なJNDIリソースとして指定する。主にwarファイルをアプリケーションサーバにデプロイする場合に利用。
JVMのシステムプロパティ JVMの起動引数に「-D設定名=値」の形式で指定する。
OSの環境変数 OSの環境変数に「設定名=値」の形式で指定する。
Spring Boot専用のプロパティファイル Spring Boot専用のプロパティファイル/YAMLファイルに「設定名=値」の形式で指定する。
@PropertySource @PropertySourceに指定した任意のプロパティファイルに「設定名=値」の形式で指定する。

Developer tools用のプロパティファイル

Spring Boot Developer toolsを適用している場合は、OSユーザーのホームディレクトリ直下の「.spring-boot-devtools.properties」に開発者毎の設定値を指定することができます。
このプロパティファイルには、アプリケーションの機能的な動作仕様に影響を与えない設定値(例えば、ログレベルなど)のみ指定するように心がけましょう。さもないと・・・あなたの環境では仕様通り動くけど、他の環境では動かない・・・といった事象を引き起こすかもしれません :scream:

@SpringBootTestproperties属性の利用

特定のテストケースクラスのみで設定値を変更したい場合は、Spring Boot 1.4から追加された@SpringBootTestproperties属性に直接設定値を指定することができます。

// ...
@SpringBootTest(properties = "app.timezone=JST")
public class IntegrationTests {
    // ...
}

@TestPropertySourceの利用

複数のテストケースクラスでテスト用の設定値を共有したい場合は、プロパティファイルを使うとよいです。

テスト用のプロパティファイルを指定
// ...
@TestPropertySource(locations = "/test.properties")
public class IntegrationTests {
    // ...
}
テスト用のプロパティファイル(test.properties)
app.timezone=JST

特定のテストケースクラスのみで設定値を変更したい場合は、properties属性に直接設定値を指定することができます。これは、前述した@SpringBootTestproperties属性で代替することができます。

// ...
@TestPropertySource(properties = "app.timezone=JST")
public class IntegrationTests {
    // ...
}

@TestPropertySourceの詳しい利用方法については、Spring Frameworkのリファレンスページをご覧ください。

コマンドライン引数の利用

アプリケーションの実行時に設定値を変更したい場合は、コマンドライン引数を使います。

$ java -jar xxx.jar --app.timezone=JST

JNDIリソースの利用

アプリケーションサーバーの同一インスタンス内に同じWebアプリケーション(war)を複数デプロイする際に、それぞれ異なる設定値に変更したい指定したい場合は、JNDIリソースを使います。
以下は、Tomcat利用時の指定例になります。

context.xml
<?xml version="1.0" encoding="utf-8"?>
<Context>
    <Environment
            type="java.lang.String"
            name="app.timezone"
            value="JST"/>
</Context>

JVMのシステムプロパティ

アプリケーションの実行時に設定値を変更したい場合は、JVMのシステムプロパティを使います。コマンドライン引数はSpring Boot独自の仕組みなのに対して、システムプロパティはJava標準の仕組みなので、Spring Bootの管理外のコンポーネントと設定値を共有したい場合は、システムプロパティの利用を検討するとよいでしょう。

$ java -Dapp.timezone=JST -jar xxx.jar

OSの環境変数

アプリケーションを実行するOSユーザー毎に設定値を変更したい場合は、OSの環境変数(ユーザー環境変数)を使います。

$ APP_TIMEZONE=JST
$ java -jar xxx.jar

環境変数(_区切り)で指定した設定値は、「.」や「-」区切りの名前で参照することができます。上記の例であれば、Spring Bootからは「app.timezone」または「app-timezone」という名前を指定して値を取得することができます。

Spring Boot専用のプロパティファイル

Spring Bootは、Spring Bootアプリケーション専用のプロパティファイル/YAMLファイル(application.propertiesまたはapplication.yml)を読み込む仕組みになっています。Spring Bootアプリケーション専用のプロパティファイル/YAMLファイルの最大の特徴は、プロファイル毎にプロパティファイル/YAMLファイルを用意できる点です。

明示的にアクティブなプロファイルを指定しない場合は、暗黙的にdefaultというプロファイルを指定したことになり、以下の順番で設定値が適用されます。

  • application-default.properties (or .yml)
  • application.properties (or .yml)

プロファイル指定時のプロパティファイルの読み込み

また、明示的にアクティブなプロファイルを指定した場合は、プロファイルの指定順とは逆順でプロパティファイル/YAMLファイルの設定値が適用されます。例えば、-Dspring.profiles.active=p1,p2とした場合は、以下の順番で設定値が適用されます。

  • application-p2.properties (or .yml)
  • application-p1.properties (or .yml)
  • application.properties (or .yml)

プロファイルのインクルード時の読み込み

さらに・・・Spring Boot専用のプロパティファイル/YAMLファイルでは、以下のように指定するとプロファイルをインクルードすることができます。

application-default.properties
spring.profiles.include=p3

プロファイルをインクルードした場合は、インクルード先のプロパティファイル/YAMLファイルが優先されます。例えば、アクティブなプロファイルを指定しなかったケースを例に見てみると、以下の順番で設定値が適用されます。

  • application-p3.properties (or .yml)
  • application-default.properties (or .yml)
  • application.properties (or .yml)

ポイントは、インクルードしたプロファイルのプロパティファイル/YAMLファイルの方が優先される点でしょう。たとえば以下のように、インクルード元でインクルード先に指定している設定値を上書きすることはできません。

application-p3.properties(インクルード先)
app.timezone=PST
application-default.properties(インクルード元)
spring.profiles.include=p3
app.timezone=JST

上記の例では、app.timezoneにはPSTが適用されます。
直感的には・・・ JSTになってほしい気がしますが・・・現時点のSpring Bootでは上書でされません。

プロパティファイルの格納場所

Spring Boot専用のプロパティファイル/YAMLファイルは、デフォルトでは以下の場所に格納することができ、以下の順番で設定値が適用されます。

  • アプリケーションの実行ディレクトリ直下の「config」ディレクトリ(file:./config/
  • アプリケーションの実行ディレクトリ(file:./
  • クラスパス直下の「config」ディレクトリ(classpath:/config/
  • クラスパス直下(classpath:/

なお、プロパティファイルのベース名前(デフォルトはapplication)の変更・格納ディレクトリの追加・対象ファイルの追加(任意のファイルを対象にする場合)などは、専用の外部設定値(spring.config.namespring.config.location)を指定することで実現することができます。詳しくは、「Spring Bootのリファレンスページ」をご覧ください。

YAMLファイルの利用

Spring Bootでは、プロパティファイルの代わりにYAMLファイルを利用することができます。YAML形式はプロパティ形式に比べてデータ構造を表現するのに優れているため、Spring Bootが提供する「Type-safe Configuration Properties」との相性がよいです。

application.yml
app:
    name: MyApp

本投稿ではYAMLファイルの使い方については説明は割愛するため、YAMLファイルを利用する場合は「Spring Bootのリファレンスページ」をご覧ください。

@PropertySourceの利用

Spring Bootでは、Spring Frameworkが提供している@PropertySourceを使用して任意のプロパティファイルを読み込むこともできます。

@SpringBootApplication
@PropertySource("classpath:security.properties")
public class SpringBootDemoApplication  {
    // ...
}

プロパティ値の置き換え(プレースホルダの利用)

プロパティファイルやYAMLファイル内では、プレースホルダを利用してプロパティ値に別の外部設定値を埋め込むことができます。

application.properties
app.name=MyApp
app.description=${app.name} is a Spring Boot application
application.yml
app:
    name: MyApp
    description: ${app.name} is a Spring Boot application

さらに、Spring Bootは乱数を埋め込むための特別の設定値(random.で始まる設定値)を提供しています。詳しくは、「Spring Bootのリファレンスページ」をご覧ください。

まとめ

今回は、Spring Bootアプリケーションで外部設定値を扱う方法について紹介してみました。外部設定の管理は、アプリケーションを作る上で絶対に欠かすことができない技術要素の一つといえます。Spring Bootの場合は、実行環境に依存する設定値はプロファイル毎のプロパティファイル/YAMLファイルに定義し、アプリケーション起動時のパラメータ(spring.profiles.acive)で利用するプロファイルを切り替えるのが標準的な管理方法になります。

なお、本投稿では触れていない部分もいくつかあるので、ちゃんと把握したい方はSpring Bootのリファレンスページを一読することをお勧めします。

参考サイト