application.ymlがどんどん肥大化して、1つのファイルの中でDB設定とSecurity設定と機能フラグが混在している、という状況になったことはありませんか。あるいは、秘密情報だけはGit管理から外したいけど、どう分離すればいいか迷ったことはないでしょうか。

Spring Boot 2.4で導入されたConfig Data APIの spring.config.import を使うと、こうした悩みがかなり楽になります。この記事では書き方と読み込み優先順位を実例で整理します。サンプルはSpring Boot 2.4以降であれば動作し、Spring Boot 3.x(Java 17/21環境)でも挙動は変わりません。

spring.config.importとは何か

spring.config.import は、application.ymlやapplication.propertiesから 追加の設定ソース を読み込むためのプロパティです。Spring Boot 2.4以降のConfig Data APIで正式にサポートされました。

注意したいのは、Spring Cloud Config Serverのようなリモート設定サーバーとは別物という点です。あくまでローカル(クラスパスやファイルシステム)にある設定ファイルを束ねる仕組みだと捉えてください。

基本の書き方

一番シンプルな形はこうです。

# application.yml
spring:
  config:
    import: classpath:db.yml

トップレベルの spring.config.import に、読み込みたいファイルを指定するだけです。ただし注意点があります。指定したファイルが見つからないと、デフォルトでは起動自体が失敗します。

org.springframework.boot.context.config.ConfigDataResourceNotFoundException:
  Config data resource 'class path resource [db.yml]' does not exist

optional:で任意ファイルを許容する

「環境によっては存在しないファイル」を扱いたい場合は optional: を前に付けます。

spring:
  config:
    import: optional:classpath:db.yml

こう書けば、ファイルがなくても起動は継続されます。ローカル開発環境専用の設定や、任意で上書きしたい秘密情報ファイルなどに向いています。

逆に「このファイルは絶対に必要」というケースでは optional: を付けないままにしておくのが安全です。欠落に気付かないまま本番で誤動作するより、起動時にはっきり失敗してくれた方がありがたいですよね。

classpath:とfile:の違い

プレフィックスはもう1種類覚えておく必要があります。

  • classpath: … jar内やresources配下を参照します。アプリに同梱する設定向けです
  • file: … 実行時のファイルシステム上のパスを参照します。外部化した設定や秘密情報向けです
spring:
  config:
    import: optional:file:./config/secrets.yml

file: を相対パスで書いた場合、基準になるのは実行時のカレントディレクトリです。IDEから起動した場合とjarを叩いた場合でカレントディレクトリが変わるため、ここでよく事故が起きます。詳しくは後述します。

複数ファイルをまとめてimportする

db.yml、security.yml、feature-flags.ymlのように分割したファイルを、一度にまとめて読み込みたい場面も出てきます。そんなときはカンマ区切りで並べて書けます。

spring:
  config:
    import: db.yml,security.yml,feature-flags.yml

ここで classpath: や file: のプレフィックスを付けていないことに気づいた方もいるかもしれません。プレフィックスを省略した場合、Spring Bootはimportを宣言した元のファイルと同じ場所を基準に解決しようとします。application.ymlがクラスパスのルート(src/main/resources直下など)にある典型的な構成では、結果的に classpath: を指定したときと同じ場所になります。ただし公式ドキュメントのImporting Additional Dataの節でも、この解決規則は構成によって結果が変わり得る前提で説明されているので、迷ったら classpath: や file: を明示したほうが安全です。

読み込み順は書いた順そのままで、 後に書いたファイルほど後から読み込まれ、同じキーがあれば後勝ちで上書き されます。例えばdb.ymlとsecurity.ymlの両方に同名のプロパティがあれば、security.ymlの値が最終的に有効になります。意図せぬ上書きを防ぐため、ファイルごとに責務をきっちり分けておくのがおすすめです。

プロファイル別ファイルとの優先順位

ここは実務でも地味につまずきやすいポイントです。プロファイルの切り替え自体の基本はSpringBootのProfileを使って環境によって違う設定を安全に切り替える方法に譲り、ここではファイルimportとの関係だけに絞って整理します。

  • application-{profile}.yml はSpring Bootが自動的に読み込む、プロファイル専用のファイルです
  • spring.config.import で明示的にimportしたファイルの中に --- 区切りで spring.config.activate.on-profile のセクションを書くと、そのセクションも同じプロファイルで有効化されます
  • 両方に同じキーがあった場合にどちらが最終的に勝つかは、Config Data APIが各ロケーションをどの順序でスタックに積んでいるかに依存し、importの書き方(パス指定や順序)によって変わり得ます

公式ドキュメントのProfile Specific Filesの節でも処理順序の詳細が解説されているので、重要な設定で両方に同じキーが存在する可能性がある場合は、本番投入前に一度自分の構成で実際にどちらが勝つかを確認しておくことを強くおすすめします。--debug オプション付きで起動してConfig Dataの読み込みログを確認するか、実際に Environment から値を出力してみるのが手軽です。

一番シンプルに事故を避ける方法は、同じキーを application-{profile}.yml とimportしたファイルの両方に書かないことです。役割を分けておけば、優先順位そのものを気にする必要がなくなります。

実践例:機能別ファイルへの分割

実務でよくあるのは、関心ごとにファイルを分ける構成です。プレフィックスを省略しているので、以下のファイルはapplication.ymlと同じ場所(クラスパスのルート)に置く前提です。

# application.yml
spring:
  config:
    import: db.yml,security.yml,feature-flags.yml
# db.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/app
    username: app_user
# feature-flags.yml
app:
  features:
    new-checkout: true
    beta-dashboard: false

このように分けておくと、機能フラグの追加・削除をするときにdb.yml側を触らずに済みます。レビュー時の差分も小さくなるので、チーム開発では地味に効いてきます。

Git管理外の秘密情報ファイルと相対パスの注意点

パスワードやAPIキーをリポジトリに含めたくない場合は、optional:file: と絶対パス(または環境変数由来のパス)を組み合わせます。

spring:
  config:
    import: optional:file:${user.home}/.app-secrets/secrets.yml

.gitignore で対象ファイルを除外しておき、ローカルでは手動配置、CI/CDではデプロイ時にファイルを置く運用にすれば、ソースコードには秘密情報が一切残りません。暗号化まで踏み込みたい場合はSpring BootのJasyptで設定を暗号化する方法と組み合わせるとさらに安心です。

なお ${user.home} のようなプレースホルダーは、システムプロパティや環境変数、コマンドライン引数など起動の早い段階で解決できる値に限られます。他のapplication.yml内のキーを同様に参照しても解決されないので注意してください。

絶対パスを使う理由は、相対パスの基準がカレントディレクトリになるからです。先ほどの file:./config/secrets.yml のような指定は、IDEでプロジェクトルートを起点に実行している間は問題なく見つかります。ところが本番でjarを /opt/app/ のような別ディレクトリから起動すると、カレントディレクトリがそこになるため同じ相対パスでは見つからず、次のような例外で落ちてしまいます。

org.springframework.boot.context.config.ConfigDataResourceNotFoundException:
  Config data resource 'file [config/secrets.yml]' does not exist

この例外メッセージの文言はSpring Bootのバージョンによって多少異なる場合があるので、完全に一致しなくても慌てずに「ファイルが見つかっていない」という事実だけ確認すれば十分です。対処法は ${user.home} のような実行場所に依存しない絶対パスに置き換えることです。これなら実行場所がどこであっても同じファイルを指しますし、optional: を付けておけばファイルが存在しない環境でも起動を継続できます。

bootstrap.yml時代からの移行者へ

Spring Cloud Configに慣れていると「設定の外部化にはbootstrap.ymlが必要」という感覚があるかもしれません。Config Data API以降は、bootstrap.ymlが読み込まれる特別な初期化フェーズは原則不要になりました。

単純な分割・外部化が目的なら spring.config.import で十分代替できます。Config Serverのようなリモート設定ソースがどうしても必要な場合だけ、別のアプローチを検討すれば良いでしょう。

まとめ

spring.config.import を使えば、application.ymlを機能別・環境別に分割しつつ、秘密情報だけを安全に外部化することもできます。optional: の有無とファイルの読み込み順、そしてプロファイル別ファイルとの優先順位は確信が持てなければ実機で確認する、という3点だけ意識しておけば、思わぬ上書きや事故に悩まされることも減るはずです。application.ymlの基本的な書き方自体はSpring Bootのapplication.yml設定ガイドも参考にしてみてください。