Spring Boot  2.4 系のプロファイルの挙動が変わった事による変更方法

# 概要
Spring Boot の [プロファイル機能である spring.profiles.include](https://docs.spring.io/spring-boot/docs/1.2.0.M1/reference/html/boot-features-profiles.html) を利用して、例えばステージングと本番の AWS の S3 バケットがちがうというような環境差異を吸収していました。アプリケーション起動時に、[spring.profiles.active](https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#spring.profiles.active) を JVM オプションに指定して環境差異を吸収できます。

>Comma-separated list of active profiles. Can be overridden by a command line switch.

Spring Boot 2.4.X にバージョンアップしたところ、これまでの挙動が変わったため、変更する必要が生じたので、その方法をメモしておきます。

参考リンク
^^^^^^^
[Change order of spring-profiles using spring.profiles.include and active profiles](https://github.com/spring-projects/spring-boot/issues/21697)
[Migrating out of spring.profiles.include into spring.profiles.group](https://github.com/spring-projects/spring-boot/issues/24172)
[Remove spring.profiles support](https://github.com/spring-projects/spring-boot/issues/22523)


## 詳細
下記のように、プロファイル名をつけた application.yml を作成していました。

```
└── src
    ├── main
    │   └── resources
    │       ├── application-development.yml
    │       ├── application-development-include1.yml
    │       ├── application-development-include2.yml
    │       ├── application.yml
    └── test
        └── resources
            ├── application-test-include.yml
            ├── application-test.yml
```

各ファイルは、環境差異を吸収するため、別途別のプロファイルを include しているので、

```
-Dspring.profiles.active=development
```

と指定すると、development に関連したプロパティをすべて取得できるようにしていました。

application.yml の内容はこのようなイメージです。(":" + "改行" でなくても、"." のみでキーを作っても問題なく動作します)

### development プロファイル

```application-development.yaml
spring.profiles.include:
  - development-include1
  - development-include2
spring:
  datasource:
    username: root
duplicate.key: "I'm a development"
```

```application-development-include1.yml
include1.development: include1。developmentプロファイルにより参照(ymlなのでUTF-8エンコードできています)
```

```application-development-include2.yml
include2.development: include2。developmentプロファイルにより参照(ymlなのでUTF-8エンコードできています)
```

### test プロファイル
```application-test.yml
spring.profiles.include: test-include
spring:
  datasource:
    username: sa
duplicate.key: "I'm a test"
```
```application-test-include.yml
include.test: this_is_test_key
```

Spring Boot 2.3.x までは、プロファイルを適切に指定することで下記が正常に動作していました。

```MyConfiguration.kt
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment


@Configuration
class MyConfiguration {
    @Bean
    fun check(env: Environment): List<String?> {

        val development1 = env.getProperty("include1.development")
        val development2 = env.getProperty("include2.development")
        val test = env.getProperty("include.test")
        val profileNameFileKey = env.getProperty("duplicate.key")

        if (env.activeProfiles.any { it == "development" }) {
            require(development1 == "include1。developmentプロファイルにより参照(ymlなのでUTF-8エンコードできています)") { "間接的なインクルードができていません" }
            require(development2 == "include2。developmentプロファイルにより参照(ymlなのでUTF-8エンコードできています)") { "間接的なインクルードができていません" }
            require(profileNameFileKey == "I'm a development") { "プロファイルと同じ名前のファイル名を読み込めていません。" }
            require(test.isNullOrEmpty()) { "development の時に test が読み込まれています。" }
        } else {
            require(development1.isNullOrEmpty()) { "test の時に、development が読み込まれています。" }
            require(development2.isNullOrEmpty()) { "test の時に、development が読み込まれています。" }
            require(test == "this_is_test_key") { "間接的にテストのキーをインクルードできていません。" }
            require(profileNameFileKey == "I'm a test") { "プロファイルと同じ名前のファイルが読み込まれていません。" }
        }

        return listOf(development1, development2, test, profileNameFileKey)
    }
}
```

Spring Boot 2.4.X にバージョンアップすると、spring.profiles.include で指定しているプロファイルがインクルードされなくなったため、include したプロファイルのキーが取得できなくなりました。

## 原因
どうやら、Spring Boot 2.4.x から、application-{profile-name}.yml にて spring.profiles.include プロパティを使って別のプロファイルを関連づけることができなくなったようです。application.yml にプロファイルの include を記述すると、すべてのプロファイルのファイルのキーと値が読み込めました。

```application.yml
spring.profiles.include:
  - development-include1
  - development-include2
  - test-include
```

しかしながら、これではすべてのファイルを読み込んでしまうので、プロファイルごとに include するファイルを変えて環境差異を吸収することができません。

## 原因および変更方法
[公式ドキュメント](https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4) によると、ConfigFileApplicationListener が複雑にしているため、変更する必要が出たとのことでした。今回のケースについては、公式が問題点としてあげている、

> ・ You can enable additional profiles from a profile specific document.
> ・ It’s hard to know the order that documents will be added.

のうち、1 つ目に該当しているように思われます。
対応案としては、[公式ドキュメント](https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4) に書かれている内容を実施することでした。

### プロファイルをグループ化する
spring.profiles.group を利用すると、プロファイルをグルーピングできると書いてあります。そのため、development と test のプロファイルについて、他に必要なプロファイルを関連づけてグループを作成します。
上述のように、プロファイルは、application.yml でしか定義できなくなったように思われますので、グループは、application.yml に記述します。

```application.yml
spring.profiles.group:
  development:
    - development-include1
    - development-include2
  test:
    - test-include
```

include するファイルのキーが利用できるようになります。プロファイルに書かれているキーはすでに読み込まれているので、問題ありません。

## マルチドキュメント機能を利用する
どうやら、Spring Boot の application.properties および application.yml は、yml のマルチドキュメント機能を実装したらしく、1 つのファイル (application.properties or application.yml) を論理的に別ドキュメントにできるようになったようです。ただ論理的に分けても意味がないので、マルチドキュメントを利用するため、Spring Boot 2.4.x から、[spring.config.activate.on-profile](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-file-activation-properties) プロパティを実装した、とのことでした。

```
test=value
#---
test=overridden-value
```

>The above example is a bit artificial since it wouldn’t really make sense to always override a value. A more common setup would be to declare that the second document is only active with a specific profile.

>In Spring Boot 2.3, you’d use the spring.profiles key to do this. With Spring Boot 2.4, we’ve decided to change the property to spring.config.activate.on-profile.

マルチドキュメントの機能によって spring.profiles.include が効くかどうかを試してみましたが、うまく動作しませんでした。on-profile 配下で指定している include ですが、どのプロファイルでアプリケーションを実行しても、すべてのプロファイルを読み込んでしまいました。公式に spring.profles.include の説名がなく、spring.config.import で説明していることから、spring.config.import を利用する必要があるようでした。ただし、application-test.yml などのテスト用のリソースについては、プロダクションコードをビルドした際は、ビルドパスに入ってこないためか、普通にファイル名を指定するとファイルが見つからない、という例外が発生してしました。そのため、[このページ](https://www.baeldung.com/properties-with-spring)に書かれている optional を記述する必要があるようでした。

```application.yml
spring.datasource.sql-script-encoding: UTF-8
---
spring:
  config:
    activate:
      on-profile: development
    import:
      - optional:application-development-include1.yml # development で動かした時は問題ないが、test のプロファイルで動かすと見つからないというエラーになるので、optional にする。
      - optional:application-development-include2.yml
---
spring:
  config:
    activate:
      on-profile: test
    import:
      - optional:application-test-include.yml # test は、production のビルドに入らないので、optional にしないといけない
```

この方法でもプロファイル名になる、application-{profile}.yml のキーはファイル名を指定せずに読み込めました。

## 所感
2 つ目の方法はなんだか微妙な挙動ですし、少し複雑さが上がっている気がするので、1 つ目の方法が良いようにも思います。ただ、公式が説明に割いている量や論理的なドキュメント分割という観点からすると、公式は、後者の方をおしているような気がしました。ただ、後者はひどく微妙な挙動だと思います。マルチドキュメントを利用しつつ、普通にプロファイルをインクルードできたら良いようにも思いましたが、ただ、spring.config.activate.on-profile と spring.profiles.active が微妙にかぶっていてややこしいような気もします。