意外と奥が深い 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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

 






 






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

@Profile("bob")
@Configuration
class BobConfiguration {
	@Bean
	fun user(): User = User("Bob")
}
1
2
3
4
5
6
7
8
9
10
11
12
13

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

./mvnw clean test -Dspring.profiles.active=alice
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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

./mvnw clean test
1









 



 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

プロファイルが指定されなかったため、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
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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

 






@Profile("default")
@Configuration
class DefaultConfiguration {
	@Bean
	fun user(): User = User("Anonymous")
}
1
2
3
4
5
6

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

./mvnw clean test
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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

./mvnw clean test -Dspring.profiles.active=zeus
1









 



 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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")
}
1
2
3
4
5
6
7

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

./mvnw clean test -Dspring.profiles.active=zeus
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

アクティブプロファイルには 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")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

./mvnw clean test -Dspring.profiles.active=elsie
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

アクティブプロファイルは 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")
}
1
2
3
4
5
6
7
8
9
10
11
12
13

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

./mvnw clean test -Dspring.profiles.active=xyz
1









 

 
 
 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
==========
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

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