日々常々

ふつうのプログラマがあたりまえにしたいこと。

SpringBootアプリケーションのProfileで制御したいとき

Short Answer

  • @Profile でBeanを変える
  • どうしても評価するなら Environment#acceptsProfiles(Profiles) を使う
  • 文字列では読まない
  • 文字列では読まない
  • 文字列では読まない

説明

SpringBootではProfileを使って設定ファイル( application-{profile}.properties とか)の読み替えを行うことが多いかと思います。 Profileは設定ファイルの切り替えだけじゃなく、と言うか、主にはBeanを切り替える機能です。 SpringBootネイティブな人だと「設定ファイルを変えるためのもの」と認識しているかもしれません。 あながち間違いじゃないです。

さて、掲題のProfileで何かを制御したいとき。処理を変えたい時。 基本的には先にあげたように @Profile を使ってのBean切り替えです。 @Profile@Conditional の仲間で、条件によってBean生成したりしなかったりする子です。

@Profile には基本的にProfile名を書きます。 @Profile("hoge") とかですね。 ですがそれだけではなく、色々な条件、たとえば !hoge & fuga とかが書けます。 詳しくはProfileのドキュメントProfiles#of(String...)のドキュメントを参照ください。 @Profile と他の @Conditional を組み合わせてのBean制御は、頑張ってなんとかできなくはないんですけど、素直に @Profile に寄せるのが吉です。

この @Profile はBeanを制御するアノテーションなんで、@Component なクラスを作るか @Bean なメソッドを作る必要があります。 「ちょっと分岐したいだけなのにいちいちクラス作るなんてとんでもない!」ってのがあるかもしれません。(賛同できませんが。)

そのような、どうしても実行時に評価したいときは、 Environmentをインジェクションするなりなんなりして acceptsProfiles メソッドで評価できます。 名前の通り引数で渡したProfileが有効かを見てくれるメソッドです。 コンテキスト起動時に決まってるものを、どうしてもリクエストごと等の実行時に常に評価したいなら、これを使いましょう。こんな表現してながら私もたまにやってたりします。

背景

こう言うことを書いているのも、よろしくない別解があって。

@Value("${hoge.fuga}") とかでプロパティが読めるのと、実行時に指定する -Dspring.profiles.active=hoge とか環境変数 SPRING_PROFILES_ACTIVE (大文字である必要はないけどなんとなく)から、こんな感じで取れるのが想像できちゃうわけで、これを稀によく見るんですよね……。

@Value("${spring.profiles.active}")
String activeProfile;

if ("hoge".equals(activeProfile)) ...

これはやっちゃいけないです。

Profileが1つだったら期待通り動くとは思います。

でもProfileって複数指定できるんですよね。 "spring.profiles.active" だし。 後からProfile追加したりとかするかもしれない。そうなった時に動かなくなるかもしれない。 String#containsは流石にないよね。カンマでsplitする?正規表現でマッチさせる? ……それっぽくはできるかもしれないけれど、Springがその通り解釈してるかは怪しいところです。 Springが解釈して実際に動いてるProfileと完全に同じでなければ、バグの温床になります。

と言うことで、先頭に書いた通り。 Profile自体の評価はSpringにお任せして、文字列として比較とか独自の評価方法は避けましょう。

誰に言われたでもないけど追記: 2021-01-20T13:00

「Profile1つしか設定しないし別にいいじゃん」みたいなのは、短期的にはその通りなんですけど、いくつか理由があって。

  • @Value("${spring.profiles.active}") と書くと、何もProfileを指定しない(default が設定される)時に「そんなプロパティないよ」ってエラーで起動しなくなります。
    • 空プロパティを定義するとか ${spring.profiles.active:} みたいな書き方したら回避できるけど、ダサいよね。
  • 継続開発の中で「Profileを増やして対応しよう」と言う手段が取れなくなる
    • Springの経験のある人が後から参画した際に「Springならこれが使えるはず」が通じなくなってしまいます。
    • こう言うのが増えると「Springを使っている」じゃなく、独自フレームワークを使ってるのと同義になってしまいます。つらい。

後者が大きいかな。「よく使われているフレームワークを使用する」のメリットを活かせる開発をしてくほうが、きっといいことあります。

余談

先頭には書きませんでしたが、Environment#acceptsProfiles(Profiles) で分岐するのではなく、 application-{Profile}.properties とかに何かしらの制御用のプロパティを定義して、Profileではなくそっちで制御するとかもあります。 Environment のようなSpringの低レベルなAPI使うより、こっちの方がいい場合もあります。 プロパティファイルの構成とか、アプリケーションの作りとかに合わせていい感じにいきましょう。

蛇足

実際にProfileがどう解決されてるかは追ってみると楽しいかもしれません。 追加されたりマージされたりしていて、おそらく想像以上のことをやってます。 たとえば default がどこで入ってるか、とか。楽しいですよね。(賛同が得られるとは思っていない