Spring Bootプロファイルの指定あれこれ

Spring Bootにはプロファイルという、環境ごとの設定やBean構成のロードを切り替える機能があります。プロファイルは、名前ベースで切り替えることができ、とても直感的に使うことができて便利です。

ところが少し変わったことをしようとすると、途端に躓いてしまう部分でもあるので、プロファイルをもう一歩踏み込んで使うためのポイントをいくつかまとめてみます。

要件

環境

  • Java 8
  • Kotlin 1.2.41
  • Spring Boot 2.0.4.RELEASE

基本的な使い方

以下のようなテストがあった場合に、userへ何がDIされるかをプロファイルを使って切り替えてみます。

@RunWith(SpringRunner::class)
@SpringBootTest
class DemoSpringProfileApplicationTests {
	@Autowired
	lateinit var user: User

	@Test
	fun contextLoads() {
		println("""
    		==========
    		${user.name}
    		==========
		""".trimIndent())
	}
}

class User(val name: String)

プロファイルは、@Profileアノテーションで名前を付けて設定します。

@Profile("alice")@Configuration
class AliceConfiguration {
	@Bean
	fun user(): User = User("Alice")
}

@Profile("bob")@Configuration
class BobConfiguration {
	@Bean
	fun user(): User = User("Bob")
}

設定されたプロファイルは、application.propertiesや、アプリケーション起動時の引数などから指定することができます。今回の内容で実行してみると以下のようになります。

./mvnw clean test -Dspring.profiles.active=alice
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : The following profiles are active: alice..  : Started DemoSpringProfileApplicationTests in 4.64 seconds (JVM running for 8.506)
==========Alice==========

このように、spring.profiles.activeで指定されたプロファイルの設定が有効化されます。

それでは、プロファイルを指定しなかった場合はどうなるでしょうか。

./mvnw clean test
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : No active profile set, falling back to default profiles: default..  : Started DemoSpringProfileApplicationTests in 1.471 seconds (JVM running for 5.367)

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 5.691 s <<< FAILURE! - in com.yo1000.demospringprofile.DemoSpringProfileApplicationTests
[ERROR] .. : No qualifying bean of type 'com.yo1000.demospringprofile.User' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

プロファイルが指定されなかったため、defaultプロファイルが選択されます。また、エラーが報告され、以下のようなメッセージが表示されます。

No qualifying bean of type 'com.yo1000.demospringprofile.User' available: expected at least 1 bean which qualifies as autowire candidate.

これはDIしようとしたオブジェクトが、DIコンテナに登録されておらず、オートワイヤリングに失敗したことを示すエラーになります。

デフォルトプロファイル

プロファイルが指定されなかった場合、Spring Bootでは、spring.profiles.defaultの設定に従い、デフォルトプロファイルを設定します。spring.profiles.defaultが設定されていない場合、defaultがデフォルトプロファイルになります。

まず、spring.profiles.defaultを設定すれば、アクティブプロファイルが未設定でも動作するのか、確認してみます。

./mvnw clean test -Dspring.profiles.default=bob
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : No active profile set, falling back to default profiles: bob..  : Started DemoSpringProfileApplicationTests in 1.95 seconds (JVM running for 3.428)
==========Bob==========

デフォルトプロファイルが指定したものに変更され、エラーが出なくなりました。

spring.profiles.defaultを設定する以外にも、未設定時にdefaultがデフォルトプロファイになるのを利用して、以下のように対応することも可能です。

@Profile("default")@Configuration
class DefaultConfiguration {
	@Bean
	fun user(): User = User("Anonymous")
}

デフォルトプロファイルも、アクティブプロファイルも指定せずに、実行してみます。

./mvnw clean test
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : No active profile set, falling back to default profiles: default..  : Started DemoSpringProfileApplicationTests in 2.245 seconds (JVM running for 3.76)
==========Anonymous==========

新たに設定された、defaultプロファイルのオブジェクトが使われるようになりました。

それでは、準備していない、異なるプロファイルを指定した場合はどうなるでしょうか。

./mvnw clean test -Dspring.profiles.active=zeus
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : The following profiles are active: zeus..  : Started DemoSpringProfileApplicationTests in 3.401 seconds (JVM running for 6.659)

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 7.747 s <<< FAILURE! - in com.yo1000.demospringprofile.DemoSpringProfileApplicationTests
[ERROR] .. : No qualifying bean of type 'com.yo1000.demospringprofile.User' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

spring.profiles.activeが指定されたため、指定されたzeusプロファイルを有効化します。当然これに対応したオブジェクトの準備はないので、エラーが報告されます。

Spring Bootでは、存在しないプロファイルが指定された場合でも、デフォルトプロファイルが設定されるわけではなく、該当のオブジェクトが見つからず、そのままエラーとなってしまいます。

複数のプロファイル(AND)

本来であれば、アクティブプロファイルが、aliceでも、bobでもない(つまり、spring.profiles.active != "alice" AND spring.profiles.active != "bob"のような)場合、デフォルトプロファイルを設定しているオブジェクトがDIコンテナに登録される、という挙動を期待したいところです。

ところが、現時点でのSpring Bootの@Profileアノテーションだけでは、これを実現することができません。そこで、@ConditionalOnExpressionアノテーションを使うことで、これを実現していきます。

defaultプロファイルを設定したクラスの記述を変更します。

@ConditionalOnExpression("'\${spring.profiles.active}' != 'alice' && '\${spring.profiles.active}' != 'bob'")//@Profile("default")
@Configuration
class DefaultConfiguration {
	@Bean
	fun user(): User = User("Anonymous")
}

アクティブプロファイルにzeusを指定して、実行してみます。

./mvnw clean test -Dspring.profiles.active=zeus
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : The following profiles are active: zeus..  : Started DemoSpringProfileApplicationTests in 3.282 seconds (JVM running for 6.438)
==========Anonymous==========

アクティブプロファイルにはzeusが指定されましたが、@ConditionalOnExpressionをつけたオブジェクトが DI されました。

@ConditionalOnExpression

@ConditionalOnExpressionはもともと、プロファイル切り替えのためだけに用意されたアノテーションではありません。

@ConditionalOnExpressionでは、SpEL(Spring Expression Language)式を、 DIコンテナへのオブジェクト登録条件として使えるようにするもので、一部の変数やプロパティを参照するための表現ができるため、プロファイルの複雑な切替条件にも対応可能になる、というわけです。

ここまでの内容で、プロファイルを使ったオブジェクトの切り替えについては、不便なく一通りのことができるはずです。ただ、プロファイルには、ここまでに紹介した以外にもいくつかの指定方法が残されているので、最後にそれらも紹介しておきます。

複数のプロファイル(OR)

複数のプロファイル条件を扱う場合、AND条件は前述の通り@Profileで対応できませんが、OR条件ならば対応可能です。

aliceプロファイルに、別の名前を追加してみます。また、@ConditionalOnExpressionにも除外条件を追加しておきます。

@Profile("alice", "allie", "elsie")@Configuration
class AliceConfiguration {
	@Bean
	fun user(): User = User("Alice")
}

@ConditionalOnExpression("""
    '${'$'}{spring.profiles.active}' != 'alice' &&
    '${'$'}{spring.profiles.active}' != 'allie' &&    '${'$'}{spring.profiles.active}' != 'elsie' &&    '${'$'}{spring.profiles.active}' != 'bob'
""")
//@Profile("default")
@Configuration
class DefaultConfiguration {
	@Bean
	fun user(): User = User("Anonymous")
}

アクティブプロファイルに、追加したelsieを指定して、実行してみます。

./mvnw clean test -Dspring.profiles.active=elsie
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : The following profiles are active: elsie..  : Started DemoSpringProfileApplicationTests in 3.11 seconds (JVM running for 4.939)
==========Alice==========

アクティブプロファイルはelsieに、出力はAliceとなり、目的のオブジェクトが選択されたことがわかります。

プロファイルの除外

プロファイルではこれらの他にも、指定されたプロファイル以外のすべて、という指定の方法もあります。以外のすべて、という条件になると、ここまでの流れで設定したプロファイルと条件が競合するため、新たに作り直します。

以下のように、除外したいプロファイル名の先頭に!をつけることで否定の意味になります。ただし、除外されたプロファイル以外のすべて、という意味になるため、使い所はあまり多くはなさそうです。

@Profile("default")
@Configuration
class DefaultConfiguration {
	@Bean
	fun user(): User = User("Default")
}

@Profile("!default")@Configuration
class NotDefaultConfiguration {
	@Bean
	fun user(): User = User("Not Default")
}

アクティブプロファイルに、default以外の任意の名前を指定して、実行してみます。

./mvnw clean test -Dspring.profiles.active=xyz
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.4.RELEASE)

..  : Starting DemoSpringProfileApplicationTests on ..
..  : The following profiles are active: xyz..  : Started DemoSpringProfileApplicationTests in 2.054 seconds (JVM running for 3.763)
==========Not Default==========

アクティブプロファイルはxyzに、出力はNot Defaultとなり、default以外のオブジェクトが選択されたことがわかります。

以上でプロファイルの使い方は、概ね紹介できたのではないかと思います。名前ベースで構成切り替えができるわかりやすさは、非常に適用しやすいので、様々な設定方法があることを知っておくと、活用の機会も広がりそうです。

参考

avatar

Written by yo1000 | YO!CHI KIKUCHI
Loves 🌱 Spring, 🦢 Pelikan fountain pen and 🦁 FINAL FANTASY VIII