Spring Boot の application.properties と @Profile アノテーションで環境を切り替える

## 概要

- Spring Boot でプロファイルを切り替えて実行する
- プロファイルの指定がない場合は Spring Framework では default プロファイルが使われる
- 自前のプロファイルとして development, test, production の3つを用意する
- test と production は一部で同じ要素を使うように構成する
- プロファイルごとの application-*.properties を用意して、記述した値を使用する
- @Profile アノテーションを使用して、指定したプロファイルに対応する bean クラスのオブジェクトを DI (Dependency injection) する
- 複数のプロファイルに対応する bean クラスを用意する
- 複数のプロファイルを指定した場合に対応する

## ソースコード一覧

```
$ tree src
src
├── main
│   ├── java
│   │   └── info
│   │       └── maigo
│   │           └── lab
│   │               └── sample
│   │                   └── profiles
│   │                       ├── MyData.java
│   │                       ├── MyDataForDefault.java
│   │                       ├── MyDataForDevelopment.java
│   │                       ├── MyDataForTestAndProduction.java
│   │                       ├── MyRestController.java
│   │                       └── ProfilesApplication.java (←今回は中身を省略)
│   └── resources
│       ├── application-development.properties
│       ├── application-production.properties
│       ├── application-test.properties
│       └── application.properties
```

## 基本部分のソースコード

### MyRestController.java

Spring Framework の DI 機能で MyData のオブジェクトをインジェクションする。
@Autowired アノテーションをインスタンス変数 myData に記述する。
http://localhost:8080/ へアクセスした際に MyData の情報を JSON で返すようにする。

```
package info.maigo.lab.sample.profiles;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyRestController {

    @Autowired
    private MyData myData;

    @RequestMapping("/")
    public MyData index() {
        return myData;
    }
}
```

### MyData.java

MyData オブジェクトの interface 定義。今回は特にメソッドは用意しない。

```java
package info.maigo.lab.sample.profiles;

public interface MyData {
}
```

### MyDataForDefault.java

default プロファイルで使用する MyData の実装クラス。
@Profile アノテーションで DI するときのプロファイルを指定できる。ここでは default を指定している。
インスタンス変数 profile には文字列 "default" を代入。
インスタンス変数 message には application.properties の message.value を代入。

```java
package info.maigo.lab.sample.profiles;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("default")
public class MyDataForDefault implements MyData {

    public String profile = "default";

    @Value("${message.value}")
    public String message;
}
```

### application.properties

default プロファイルで使用するプロパティファイル。

```
message.value=Hello, default!
```

## default プロファイルで起動する

ビルドして作った JAR ファイルを指定して起動する。プロファイルを指定するパラメータは付けない。

```
$ java -jar target/profiles-0.0.1-SNAPSHOT.jar
```

起動したサーバにアクセスすると default プロファイルの情報が読み込まれているのがわかる。

```
$ curl http://localhost:8080/
{"profile":"default","message":"Hello, default!"}
```

## development プロファイルで使用するソースコード

### MyDataForDevelopment.java

development プロファイルで使用する MyData の実装クラス。
@Profile アノテーションにて develop プロファイルを指定。
インスタンス変数 profile に文字列 "development" を代入。
インスタンス変数 message に application-development.properties の message.value を代入。

```java
package info.maigo.lab.sample.profiles;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("development")
public class MyDataForDevelopment implements MyData {

    public String profile = "development";

    @Value("${message.value}")
    public String message;
}
```

### application-development.properties

development プロファイルで使用するプロパティファイル。

```
message.value=Hello, development!
```

## development プロファイルを指定して起動する

JVM パラメータ -Dspring.profiles.active に development を指定する。

```
$ java -Dspring.profiles.active=development -jar target/profiles-0.0.1-SNAPSHOT.jar
```

起動したサーバにアクセスすると development プロファイルの情報が読み込まれているのがわかる。

```
$ curl http://localhost:8080/
{"profile":"development","message":"Hello, development!"}
```

## test プロファイルと production プロファイルで使用するソースコード

### MyDataForTestAndProduction.java

test プロファイルと production プロファイルで使用する MyData の実装クラス。
@Profile アノテーションに test と production を指定している。指定したいずれかのプロファイルが指定されたときにこのクラスのオブジェクトが DI 対象となる。
@RestController にて返却する JSON の profile は、このクラスの getProfile メソッドにて生成する。Environment#getActiveProfiles で複数指定されたプロファイル名が取得できる。

```java
package info.maigo.lab.sample.profiles;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
@Profile({"test", "production"})
public class MyDataForTestAndProduction implements MyData {

    @Autowired
    private Environment env;

    public String getProfile() {
        return String.join(",", env.getActiveProfiles());
    }

    @Value("${message.value}")
    public String message;
}
```

### application-test.properties

test プロファイルで使用するプロパティファイル。

```
message.value=Hello, test!
```

### application-production.properties

production プロファイルで使用するプロパティファイル。

```
message.value=Hello, production!
```

## test プロファイルを指定して起動する

JVM パラメータ -Dspring.profiles.active に test を指定する。

```
$ java -Dspring.profiles.active=test -jar target/profiles-0.0.1-SNAPSHOT.jar
```

起動したサーバにアクセスすると test プロファイルの情報が読み込まれているのがわかる。

```
$ curl http://localhost:8080/
{"message":"Hello, test!","profile":"test"}
```

## production プロファイルを指定して起動する

JVM パラメータ -Dspring.profiles.active に production を指定する。

```
$ java -Dspring.profiles.active=production -jar target/profiles-0.0.1-SNAPSHOT.jar
```

起動したサーバにアクセスすると production プロファイルの情報が読み込まれているのがわかる。

```
$ curl http://localhost:8080/
{"message":"Hello, production!","profile":"production"}
```

## 複数のプロファイル test と production を指定して起動する

JVM パラメータ -Dspring.profiles.active にカンマ区切りで複数のプロファイル "test,production" を指定する。

```
$ java -Dspring.profiles.active=test,production -jar target/profiles-0.0.1-SNAPSHOT.jar
```

起動したサーバにアクセスすると、profile 項目の値に "test,production" と入っているため test と production 両方のプロファイルの情報が読み込まれているのがわかる。

```
$ curl http://localhost:8080/
{"message":"Hello, production!","profile":"test,production"}
```

application-test.properties と application-production.properties に定義した message.value の値は、競合した結果 application-production.properties の値が優先された (競合すべき値を設定すべきではないと考えられる)。

試しにプロファイル指定の順番を逆にして "production,test" を指定してみたら、application-test.properties の値が優先された。

```
$ java -Dspring.profiles.active=production,test -jar target/profiles-0.0.1-SNAPSHOT.jar
```

```
$ curl http://localhost:8080/
{"message":"Hello, test!","profile":"production,test"}
```

おそらく、後から指定したプロファイルが優先される (上書きされる？) と思われる。

## 今回の環境

- OpenJDK 11
- Spring Boot 2.2.0.M4

## 参考資料
- [4.2. Externalized Configuration - Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/docs/2.2.0.M4/reference/htmlsingle/#boot-features-external-config)
- [4.3. Profiles - Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/docs/2.2.0.M4/reference/htmlsingle/#boot-features-profiles)
- [Profile \(Spring Framework 5\.2\.0\.M3 API\)](https://docs.spring.io/spring/docs/5.2.0.M3/javadoc-api/org/springframework/context/annotation/Profile.html)
- [Springでprofileアノテーションによるbeanの切り替え \- Qiita](https://qiita.com/kagamihoge/items/c2146fd61b4ee19cc85a)
- [Spring BootのProfilesを使いこなす \| 川上徹](https://xn--fhqq99a3lb.jp/299)
- [Spring Bootプロファイルの指定あれこれ \| Then go talk to a wall](https://www.yo1000.com/spring-boot%E3%83%97%E3%83%AD%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%8C%87%E5%AE%9A%E3%81%82%E3%82%8C%E3%81%93%E3%82%8C)