これは、なにをしたくて書いたもの?
Spring Frameworkで、プロパティファイルをJavaConfigにマッピングする場合、あとProfileも含めて…という時は
どうするんだっけ?というのをよく忘れるので。
メモしておこうかなと。
Spring Frameworkで、プロパティファイルを扱う
Spring Bootでこう言うと、まず真っ先に挙がるのはapplication.propertiesだと思います。
自分で用意するプロパティファイルを扱う場合は、どうするのでしょう?
あと、Profileについても。
環境に応じて設定値を変更する場合は環境変数で、という話にしたいところかなとは思いますが、これはこれで押さえて
おきたい内容ではあるので今回扱おうと思います。
@PropertySource
自分で用意したプロパティファイルを読む場合は、@PropertySourceアノテーションを使うのかな、と。
PropertySource (Spring Framework 5.3.6 API)
Javadocに書かれているように、@PropertySourceアノテーション+@Configurationアノテーションを使うことで
プロパティファイルの内容をSpringに組み込むことができます。
※@Configurationアノテーションではなく、@Componentアノテーションでもいいのですが
@Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig {
また、プロパティファイル内にマルチバイト文字を含む場合は、encodingを指定して読み込むようにすればOKです。
@Configuration @PropertySource(value = "classpath:/com/myco/app.properties", encoding = "UTF-8") public class AppConfig {
SpELも使えるので、Profiileごとにプロパティファイルを用意したい場合は、以下のようにすればよいでしょう。
@Configuration @PropertySource(value = "classpath:/com/myco/app-${spring.profiles.active}.properties", encoding = "UTF-8") public class AppConfig { .... }
また、複数のプロパティファイルを読み込みたい場合は、@PropertySourcesアノテーションを使うことで
@PropertySourceアノテーションを複数指定することができます。
PropertySources (Spring Framework 5.3.6 API)
こんな感じですね。書いた順に読み込まれる(後勝ち)ようです。
@Configuration @PropertySources({ @PropertySource(value = "classpath:/com/myco/app.properties", encoding = "UTF-8"), @PropertySource(value = "classpath:/com/myco/app-${spring.profiles.active}.properties", encoding = "UTF-8") }) public class AppConfig { .... }
ただ、この書き方だと少なくとも2つのプロパティファイルが存在している必要があり、どちらかが存在しない場合は
例外がスローされます。
それで困る場合は、ignoreResourceNotFound属性をtrueにすれば、指定のファイルが存在しない場合に無視することが
できます。
たとえば、Profile指定のプロパティファイルが存在しないケースがある場合は、以下のような指定になります。
@Configuration @PropertySources({ @PropertySource(value = "classpath:/com/myco/app.properties"), @PropertySource(value = "classpath:/com/myco/app-${spring.profiles.active}.properties", encoding = "UTF-8", ignoreResourceNotFound = true) }) public class AppConfig { .... }
@ConfigurationPropertiesか@Valueか
値の取得には、@ConfigurationPropertiesアノテーションまたは@Valueアノテーションを使います。
ConfigurationProperties (Spring Boot 2.4.5 API)
Value (Spring Framework 5.3.6 API)
2つの方法の違いは、こちらに記述があります。
@ConfigurationProperties vs. @Value
@ConfigurationPropertiesアノテーションを使う場合、Relaxed Binding(プロパティ名から@ConfigurationPropertiesが
付与されたBeanのプロパティに一定のルールでマッピングしてくれる機能)が使える、メタデータの生成ができて補完で
便利などのポイントがあります。
一方で、SpELが使えるのは@Valueだけです。
とはいえ、プロパティを型安全に扱えるのは@ConfigurationPropertiesアノテーションなので、基本的にはこちらを使うのが
よさそうです。
Type-safe Configuration Properties
Relaxed Bindingではprefix以降の部分のプロパティ名は、Kebab case、Camel case、アンダースコア(大文字、小文字)での
変換をサポートしています。
ドキュメントの例でいくと、acme.my-project.person.[プロパティ名]というプロパティキーで@ConfigurationProperties
アノテーションでacme.my-project.personをprefixとした場合、BeanのfirstNameというプロパティには以下の4パターンの
プロパティ名がバインドできます。
acme.my-project.person.first-name(推奨)acme.myProject.person.firstNameacme.my_project.person.first_nameACME_MYPROJECT_PERSON_FIRSTNAME(環境変数で指定する場合の推奨)
プロパティファイル内で記述する分には、Kebab caseで定義するのが良さそうです。
ちなみに、@ConfigurationPropertiesアノテーションはBeanへ値をバインドする仕組みであるため、値のバインドには
getter/setter、またはコンストラクタでのインジェクションが必要になります。
今回はgetter/setterを使おうと思います。
こんな感じの例になりますね。
@ConfigurationProperties(prefix="acme.my-project.person") public class OwnerProperties { // 以下の4つのパターンをバインド可能 // acme.my-project.person.first-name // acme.myProject.person.firstName // acme.my_project.person.first_name // ACME_MYPROJECT_PERSON_FIRSTNAME private String firstName; // getter/setterが必要 public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
@ConfigurationPropertiesScan
@ConfigurationPropertiesアノテーションを付与したクラスは、@ConfigurationPropertiesScanアノテーションを合わせて
使うことでBeanとして検出・登録することができます。
Enabling @ConfigurationProperties-annotated types
ConfigurationPropertiesScan (Spring Boot 2.4.5 API)
Spring Boot 2.2から使えるアノテーションだそうです。
@ConfigurationProperties scanning
それ以前は、@EnableConfigurationPropertiesアノテーションで同じことをしていたのだとか。
といっても、@ConfigurationPropertiesScanアノテーションは@EnableConfigurationPropertiesアノテーションが付与された
メタアノテーションなんですけどね。
こんなイメージで使うことになります。
@SpringBootApplication @ConfigurationPropertiesScan public class MyApplication { .... } @ConfigurationProperties(prefix = "acme.my-project") public class MyProperties { .... }
これで、この例だとMyPropertiesクラスはBeanとして検出されDIが可能になります。
@PropertySourceと@ConfigurationPropertiesScan
ここまで書くと、自分でプロパティファイルを用意した場合は以下のように書けばいいのでは?と思うのですが、これは
うまくいきません。
@SpringBootApplication @ConfigurationPropertiesScan public class MyApplication { .... } @PropertySource(value = "classpath:/com/myco/app.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "acme.my-project") public class MyProperties { .... }
この例でいくとMyPropertiesはBeanとして登録されるのですが、肝心のプロパティ値がインジェクションされない状態に
なってしまいます。
それを回避したければ@Configurationアノテーションを付与するか
@SpringBootApplication // @ConfigurationPropertiesScan // この場合、@ConfigurationPropertiesScanは意味をなさなくなる public class MyApplication { .... } @Configuration @PropertySource(value = "classpath:/com/myco/app.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "acme.my-project") public class MyProperties { .... }
@PropertySourceアノテーションを付与するクラスと、@ConfigurationPropertiesアノテーションを付与するクラスを
別々にするか、という気がします。
@SpringBootApplication @ConfigurationPropertiesScan public class MyApplication { .... } @Configuration @PropertySource(value = "classpath:/com/myco/app.properties", encoding = "UTF-8") public class MyProperties { .... } @ConfigurationProperties(prefix = "acme.my-project") public class MyConfig { .... }
これは、@ConfigurationPropertiesでプロパティをインジェクションする対象として、@PropertySourceを指定した
自身を使うのはダメってことなんでしょうね。
PropertiesFactoryBean
余談的には、Propertiesのインスタンスとして組み込む場合はPropertiesFactoryBeanも使えます。
PropertiesFactoryBean (Spring Framework 5.3.6 API)
と、アノテーションなどの説明はこれくらいにして、実際に試してみましょう。
環境
今回の環境は、こちらです。
$ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-72-generic", arch: "amd64", family: "unix"
準備
Spring Initializrで、プロジェクトの作成を行います。今回は、依存関係は特に追加しませんでした。
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=2.4.5 \ -d javaVersion=11 \ -d name=profile-spec-configuration \ -d groupId=org.littlewings \ -d artifactId=profile-spec-configuration \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.spring.configuration \ -d baseDir=profile-spec-configuration | tar zxvf - $ cd profile-spec-configuration $ find src -name '*.java' | xargs rm
含まれていたソースコードは、1度削除。
<properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
こちらをベースに進めていきましょう。
お題
@PropertySourceアノテーションおよび@ConfigurationPropertiesアノテーションを使い、プロパティファイルを読み込みつつ
Beanのプロパティにマッピングしていきます。
この時に、Profileを指定したバリエーションも試してみましょう。
mainクラス
いきなりですが、mainクラスはこのような形で作成。
src/main/java/org/littlewings/spring/configuration/App.java
package org.littlewings.spring.configuration; import java.util.List; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.core.env.Environment; @SpringBootApplication public class App implements CommandLineRunner { Environment environment; MyConfiguration myConfiguration; ProfileSpecConfiguration profileSpecConfiguration; public App( Environment environment, MyConfiguration myConfiguration, ProfileSpecConfiguration profileSpecConfiguration ) { this.environment = environment; this.myConfiguration = myConfiguration; this.profileSpecConfiguration = profileSpecConfiguration; } public static void main(String... args) { SpringApplication.run(App.class, args); } @Override public void run(String... args) throws Exception { System.out.println("================================================="); System.out.printf("current Profile = %s%n", List.of(environment.getActiveProfiles())); System.out.println(); System.out.println("================================================="); System.out.println("print MyConfiguration properties"); System.out.printf(" message = %s%n", myConfiguration.getMessage()); System.out.printf(" count = %d%n", myConfiguration.getCount()); System.out.println(); System.out.println("================================================="); System.out.println("print ProfileSpecConfiguration properties"); System.out.printf(" message1 = %s%n", profileSpecConfiguration.getMessage1()); System.out.printf(" message2 = %s%n", profileSpecConfiguration.getMessage2()); System.out.printf(" count = %d%n", profileSpecConfiguration.getCount()); System.out.println(); System.out.println("================================================="); System.out.println("print application properties"); System.out.printf(" spring.application.name = %s%n", environment.getProperty("spring.application.name")); System.out.println(); System.out.println("================================================="); } }
こちらの2つのクラスは、自分で作成したプロパティファイルの値をマッピングしたクラスになります。
MyConfiguration myConfiguration;
ProfileSpecConfiguration profileSpecConfiguration;
Environmentは、現在のProfileの確認やapplication.propertiesの確認に使います。
Environment environment;
Profileを変えたりしつつ、以下の部分で実際に適用されているプロパティ値を確認してきましょう。
@Override public void run(String... args) throws Exception { System.out.println("================================================="); System.out.printf("current Profile = %s%n", List.of(environment.getActiveProfiles())); System.out.println(); System.out.println("================================================="); System.out.println("print MyConfiguration properties"); System.out.printf(" message = %s%n", myConfiguration.getMessage()); System.out.printf(" count = %d%n", myConfiguration.getCount()); System.out.println(); System.out.println("================================================="); System.out.println("print ProfileSpecConfiguration properties"); System.out.printf(" message1 = %s%n", profileSpecConfiguration.getMessage1()); System.out.printf(" message2 = %s%n", profileSpecConfiguration.getMessage2()); System.out.printf(" count = %d%n", profileSpecConfiguration.getCount()); System.out.println(); System.out.println("================================================="); System.out.println("print application properties"); System.out.printf(" spring.application.name = %s%n", environment.getProperty("spring.application.name")); System.out.println(); System.out.println("================================================="); }
実行は、以下のコマンドで行います。
## Profileを指定しない場合 $ mvn spring-boot:run ## Profileを指定する場合 $ mvn spring-boot:run -Dspring-boot.run.profiles=[Profile名]
では、プロパティファイルをマッピングするクラスを書いていきます。
単一のプロパティファイルを扱う
まずは、Profileといったものを意識しない、単一のプロパティファイルを扱うクラスを書いていきます。
こんな感じで作成。
src/main/java/org/littlewings/spring/configuration/MyConfiguration.java
package org.littlewings.spring.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource(value = "classpath:/my-configuration.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "my.configuration") public class MyConfiguration { String message; int count; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
プロパティファイルは、こんな内容で用意。
src/main/resources/my-configuration.properties
my.configuration.message=こんにちは、世界 my.configuration.count=10
@ConfigurationPropertiesScanアノテーションは使用しません。
@SpringBootApplication public class App implements CommandLineRunner {
これで、以下の内容を確認します。
System.out.println("================================================="); System.out.printf("current Profile = %s%n", List.of(environment.getActiveProfiles())); System.out.println(); System.out.println("================================================="); System.out.println("print MyConfiguration properties"); System.out.printf(" message = %s%n", myConfiguration.getMessage()); System.out.printf(" count = %d%n", myConfiguration.getCount()); System.out.println(); System.out.println("=================================================");
先ほどソースコードを載せた時に、この後ろにProfileごとにプロパティファイルを用意したクラスの内容を表示する処理が
ありましたが、今回は無視してください。
実行。
$ mvn spring-boot:run
結果。
================================================= current Profile = [] ================================================= print MyConfiguration properties message = こんにちは、世界 count = 10 =================================================
プロパティファイルの内容が取得できていることが、確認できました。あと、Profileは指定していないので空ですね。
ここで、@Configurationアノテーションを削除して
@PropertySource(value = "classpath:/my-configuration.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "my.configuration") public class MyConfiguration {
@ConfigurationPropertiesScanアノテーションを付与して実行すると
@SpringBootApplication @ConfigurationPropertiesScan public class App implements CommandLineRunner {
MyConfigurationクラスをBeanとして扱えてはいるものの、プロパティ値が取得できなくなります。
================================================= current Profile = [] ================================================= print MyConfiguration properties message = null count = 0 =================================================
先述の通り、@ConfigurationPropertiesアノテーションを付与したクラスを@ConfigurationPropertiesScanアノテーションで
検出するようにして、かつ一緒に@PropertySourceアノテーションを付与してもうまくいきません。
というわけで、@ConfigurationPropertiesアノテーションを付与するクラスと@PropertySourceアノテーションを
付与するクラスを分離してみます。
@Configuration @PropertySource(value = "classpath:/my-configuration.properties", encoding = "UTF-8") class MyConfigurationProperties {} @ConfigurationProperties(prefix = "my.configuration") public class MyConfiguration {
mainクラスは、そのままです。
@SpringBootApplication @ConfigurationPropertiesScan public class App implements CommandLineRunner {
今度はうまくいきます。
=================================================
current Profile = []
=================================================
print MyConfiguration properties
message = こんにちは、世界
count = 10
=================================================
これでちょっと悩んだので、メモ的に…。
Profileに応じたプロパティファイルを読むようにする
次は、Profileに応じたプロパティファイルを読むようにしてみましょう。
mainクラスからは、@ConfigurationPropertiesScanアノテーションは削除しました。
@SpringBootApplication public class App implements CommandLineRunner {
プロパティファイルは、3つ用意します。
Profileなし。
src/main/resources/profile-spec-configuration.properties
profile.spec.configuration.message1=default message1 profile.spec.configuration.message2=default message2 profile.spec.configuration.count=1
developというProfile相当。Profileなしのものから、ひとつ項目を減らしています。
src/main/resources/profile-spec-configuration-develop.properties
profile.spec.configuration.message1=develop profile message1 profile.spec.configuration.count=5
productionというProfile相当。
src/main/resources/profile-spec-configuration-production.properties
profile.spec.configuration.message1=production profile message1 profile.spec.configuration.message2=production profile only message2 profile.spec.configuration.count=10
用意したプロパティファイルは、Profileなしのもの、Profile指定ありのものを両方読むようにしてみます。
プロパティファイルの内容をマッピングするクラスは、こちら。@PropertySourceアノテーションについては、あとで
記載します。
src/main/java/org/littlewings/spring/configuration/ProfileSpecConfiguration.java
package org.littlewings.spring.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources; @Configuration // あとで @ConfigurationProperties(prefix = "profile.spec.configuration") public class ProfileSpecConfiguration { String message1; String message2; int count; public String getMessage1() { return message1; } public void setMessage1(String message1) { this.message1 = message1; } public String getMessage2() { return message2; } public void setMessage2(String message2) { this.message2 = message2; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
これで、mainクラスのこちらの部分の出力を確認していきます。
System.out.println("================================================="); System.out.printf("current Profile = %s%n", List.of(environment.getActiveProfiles())); System.out.println(); System.out.println("================================================="); // 省略 System.out.println("print ProfileSpecConfiguration properties"); System.out.printf(" message1 = %s%n", profileSpecConfiguration.getMessage1()); System.out.printf(" message2 = %s%n", profileSpecConfiguration.getMessage2()); System.out.printf(" count = %d%n", profileSpecConfiguration.getCount()); System.out.println(); System.out.println("=================================================");
まずは、こちらの定義で。@PropertySourcesアノテーションを使い、@PropertySourceアノテーションを複数指定
できるようにしています。
PropertySources (Spring Framework 5.3.6 API)
@Configuration @PropertySources({ @PropertySource("classpath:/profile-spec-configuration.properties"), @PropertySource("classpath:/profile-spec-configuration-${spring.profiles.active}.properties") }) @ConfigurationProperties(prefix = "profile.spec.configuration") public class ProfileSpecConfiguration {
確認してみます。
develop Profile。
$ mvn spring-boot:run -Dspring-boot.run.profiles=develop
profile-spec-configuration-develop.propertiesファイルとprofile-spec-configuration.propertiesファイルの両方に
定義されているものはprofile-spec-configuration-develop.propertiesファイルの方が優先され、そうでないものは
profile-spec-configuration.propertiesの方が残っていますね。
================================================= current Profile = [develop] ================================================= print ProfileSpecConfiguration properties message1 = develop profile message1 message2 = default message2 count = 5 =================================================
production Profile指定。
$ mvn spring-boot:run -Dspring-boot.run.profiles=production
先ほどの結果から予想できますが、こちらはすべての内容がprofile-spec-configuration-production.propertiesファイルの内容で
上書きされるようです。
================================================= current Profile = [production] ================================================= print ProfileSpecConfiguration properties message1 = production profile message1 message2 = production profile only message2 count = 10 =================================================
では、@PropertySourceアノテーションの順番を入れ替えてみましょう。
@Configuration @PropertySources({ @PropertySource("classpath:/profile-spec-configuration-${spring.profiles.active}.properties"), @PropertySource("classpath:/profile-spec-configuration.properties") }) @ConfigurationProperties(prefix = "profile.spec.configuration") public class ProfileSpecConfiguration {
develop Profile。
$ mvn spring-boot:run -Dspring-boot.run.profiles=develop
すると、内容がすべてprofile-spec-configuration.propertiesファイルのものになります。
================================================= current Profile = [develop] ================================================= print ProfileSpecConfiguration properties message1 = default message1 message2 = default message2 count = 1 =================================================
production Profile指定でも同じです。
$ mvn spring-boot:run -Dspring-boot.run.profiles=production
結果。
================================================= current Profile = [production] ================================================= print ProfileSpecConfiguration properties message1 = default message1 message2 = default message2 count = 1 =================================================
つまり、@PropertySourcesアノテーション内に指定した@PropertySourceアノテーションに定義したプロパティファイル内に
キーが重複したものがあれば、後勝ちになるということですね。
まあ、重複させない方がいいのかな、とは思います…。
ところで、ここまでの定義(どちらでも構いません)で、以下のようなプロパティファイルを用意していないProfileで実行すると
$ mvn spring-boot:run -Dspring-boot.run.profiles=staging
対応するプロパティファイルが存在しない、ということで例外がスローされます。
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [org.littlewings.spring.configuration.App]; nested exception is java.io.FileNotFoundException: class path resource [profile-spec-configuration-staging.properties] cannot be opened because it does not exist
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:189) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:331) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:782) ~[spring-boot-2.4.5.jar:2.4.5]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:774) ~[spring-boot-2.4.5.jar:2.4.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-2.4.5.jar:2.4.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:339) ~[spring-boot-2.4.5.jar:2.4.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1340) ~[spring-boot-2.4.5.jar:2.4.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1329) ~[spring-boot-2.4.5.jar:2.4.5]
at org.littlewings.spring.configuration.App.main(App.java:31) ~[classes/:na]
Caused by: java.io.FileNotFoundException: class path resource [profile-spec-configuration-staging.properties] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:187) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.EncodedResource.getInputStream(EncodedResource.java:159) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:110) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:81) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.PropertiesLoaderUtils.loadProperties(PropertiesLoaderUtils.java:67) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.ResourcePropertySource.<init>(ResourcePropertySource.java:67) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.core.io.support.DefaultPropertySourceFactory.createPropertySource(DefaultPropertySourceFactory.java:37) ~[spring-core-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(ConfigurationClassParser.java:463) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:280) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:304) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:207) ~[spring-context-5.3.6.jar:5.3.6]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:175) ~[spring-context-5.3.6.jar:5.3.6]
... 13 common frames omitted
つまり、@PropertySourceアノテーションで指定したファイルは、今の定義では必須だということです。
これが嫌な場合、存在しないことがありうるケースでは、@PropertySourceアノテーションのignoreResourceNotFound属性を
trueにするとよいでしょう。
@Configuration @PropertySources({ @PropertySource("classpath:/profile-spec-configuration.properties"), @PropertySource(value = "classpath:/profile-spec-configuration-${spring.profiles.active}.properties", ignoreResourceNotFound = true) }) @ConfigurationProperties(prefix = "profile.spec.configuration") public class ProfileSpecConfiguration {
この場合は、profile-spec-configuration.propertiesファイルだけが必須になっています。
もう1度実行。
$ mvn spring-boot:run -Dspring-boot.run.profiles=staging
今度は実行に成功し、profile-spec-configuration.propertiesファイルの内容が表示されます。
profile-spec-configuration-staging.propertiesファイルという存在しないものは、無視されました、と。
================================================= current Profile = [staging] ================================================= print ProfileSpecConfiguration properties message1 = default message1 message2 = default message2 count = 1 =================================================
application.propertiesはどうやっているのか?
ところでapplication.propertiesについては、Profileに対応していないプロパティファイルが存在していなくてもエラーに
なりません。
というか、けっこう融通効きますよね。どうなっているんでしょう。
こちらを見返してみます。
こんなことが書かれています。
Config data files are considered in the following order: 1. Application properties packaged inside your jar (application.properties and YAML variants). 2. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants). 3. Application properties outside of your packaged jar (application.properties and YAML variants). 4. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
確認してみましょう。spring.application.nameを扱ってみます。
Profile指定なし。
src/main/resources/application.properties
spring.application.name=Default Application Name
develop Profile(中身なし)。
src/main/resources/application-develop.properties
production Profile。
src/main/resources/application-production.properties
spring.application.name=Production Application Name
mainクラスの、こちらの部分で確認してみましょう。
System.out.println("================================================="); System.out.printf("current Profile = %s%n", List.of(environment.getActiveProfiles())); System.out.println(); System.out.println("================================================="); // 省略 System.out.println("print application properties"); System.out.printf(" spring.application.name = %s%n", environment.getProperty("spring.application.name")); System.out.println(); System.out.println("=================================================");
Profile指定なし。
$ mvn spring-boot:run
結果。
================================================= current Profile = [] ================================================= print application properties spring.application.name = Default Application Name =================================================
develop Profile。
$ mvn spring-boot:run -Dspring-boot.run.profiles=develop
結果。
================================================= current Profile = [develop] ================================================= print application properties spring.application.name = Default Application Name =================================================
production Profile。
$ mvn spring-boot:run -Dspring-boot.run.profiles=production
結果。
================================================= current Profile = [production] ================================================= print application properties spring.application.name = Production Application Name =================================================
つまり、後勝ちですね。
存在しないProfile。
$ mvn spring-boot:run -Dspring-boot.run.profiles=staging
こちらは、問題なく動きます。
================================================= current Profile = [staging] ================================================= print application properties spring.application.name = Default Application Name =================================================
ソースコードはどうなっているか?というと、専用の処理が用意されているみたいですね。
まとめ
Spring Boot/Spring Frameworkで、プロパティファイルをJavaConfigにマッピングする方法、あとProfileの扱い方は?
というのを見返してみました。
非常によく忘れるので…ここまでまとめておけば、メモとしては使えるかな、と。
あと、ちゃんと挙動の確認ができたので、やっておいて良かったかなと思います。