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
以外のオブジェクトが選択されたことがわかります。
以上でプロファイルの使い方は、概ね紹介できたのではないかと思います。名前ベースで構成切り替えができるわかりやすさは、非常に適用しやすいので、様々な設定方法があることを知っておくと、活用の機会も広がりそうです。