【Java・Spring Boot】YAML形式のプロパティファイルから値を取得する方法

# 本記事の内容
この記事では、
Spring BootのプロジェクトでYAML(YML)形式のプロパティファイルから値を取得する方法について解説しています。


## 記事を書く経緯
一般的に、ハードコーディングを避けるため、システム内で使用する値をプロパティファイルに記載して外部化する場面があるかと思います。
Java/SpringBootでは、自動生成される`application.propeties`をプロパティファイル（設定ファイル）として使っていましたが、JSON形式のように読めて可読性も高いため、YAML形式のプロパティファイル`application.yml`を採用しました。

デフォルトのプロパティファイルとなる`application.yml`の他に、メッセージを管理するプロパティファイル`message.yml`も必要となり、これを作成したところ、設定値が取得できずに詰まったので今回記事にします。


## 前提条件
下記の前提条件で、Spring Bootのプロジェクトを作成済みとします。
|使用技術  | バージョン |
|:-----|:--------|
|Spring Boot |2.7.2 |



# 解説
## YAML形式のプロパティファイルから値が取得できない原因
調査を行うと、
まず、デフォルトのプロパティファイルとなる`application.yml`以外のファイルは、`@PropertySource`アノテーションを使って明示的にロードさせる必要があるとわかりました。
しかし、公式ドキュメントなどを漁って調査したところ、yaml形式のファイルは、単純に`@PropertySource`アノテーションのvalue属性に指定してもロードができないようです。

```java:NGパターン
@PropertySource(value = "classpath:message.yml")
public class sampleClass {
  // プロパティを使う処理
  // =>値を読み込めない！
}
```
下記のドキュメントを読むと、その旨の記載があります。

https://docs.spring.io/spring-boot/docs/1.5.22.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-yaml-shortcomings


### yaml形式のプロパティファイルを読み込む方法
ではyaml形式のプロパティファイルは使えないのかというともちろんそうではありません。
yaml形式のプロパティファイルをロードするには、`@PropertySource`の`factory`属性を利用して、YAMLファイルを処理する`PropertySourceFactory`のカスタム実装を提供することでロードが可能になり、値を参照することができます。

簡単に説明すると、プロパティを参照したいクラスが、yamlファイルを読み込むことができるfactoryという仲介者に頼んで、代わりにロードしてもらうことで、プロパティを参照したいクラスはyaml形式のファイルを読み込めるようになるというイメージです。

## 実装
今回はこのプロパティファイルを例にしていきます。
やることとしては、このような流れです。
- `message.yml`にメッセージとなる値を定義
- `PropertySourceFactory`をカスタム実装
- プロパティ利用クラス`MessageUtil.java`で値を取得

### message.ymlにメッセージとなる値を定義
```yaml:message.yml
message:
  error:
    E101: システムエラーが発生しました。
    E102: システムエラーが発生しました。
```



### PropertySourceFactoryのカスタム実装


```Java:YamlPropertySourceFactory.java
import java.io.IOException;
import java.util.Properties;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

/** Yamlファイルをプロパティファイルとして読み込むためのファクトリクラス */
public class YamlPropertySourceFactory implements PropertySourceFactory {

  @Override
  public PropertySource<?> createPropertySource(String name, EncodedResource resource)
    throws IOException {
      YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
      factory.setResources(resource.getResource());

      // YAML形式のリソースをjava.util.Properties オブジェクトに変換
      Properties properties = factory.getObject();

      // ラッパーであるPropertiesPropertySourceのインスタンスを返却し、解析されたプロパティを読み取れるようにする
      return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
  }
}
```

### プロパティ利用クラス`MessageUtil.java`で値を取得
今回はMapで受け取るサンプルです。
```Java:MessageUtil.java
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.example.App.config.YamlPropertySourceFactory;
import lombok.Setter;

/** メッセージ取得を提供するUtilityクラス */
@Component
@ConfigurationProperties(prefix = "message")
@PropertySource(value = "classpath:message.yml", factory = YamlPropertySourceFactory.class)
public class MessageUtil {

  @Setter
  private Map<String, String> error;

  /**
   * エラーメッセージ取得
   * @param code エラーコード
   * @return メッセージ
   */
  public String getErrMessage(String code) {
    return this.error.get(code);
  }
}
```

### コンソールで確認
```java:Main.java

public class Main {

  public static void main(String[] args) {
    MessageUtil messageUtil = new MessageUtil();
    System.out.println(messageUtil.getErrMessage("E101"));
  }
}
```

### 実行結果
```console:console
システムエラーが発生しました。
```

# まとめ
- `applicaton.properties`たは、`applicaton.yaml(applicaton.yml)`はデフォルトで読み込んでくれるが、別のプロパティファイルを自作した場合は、`@PropertySource`でロードする必要がある。
- yaml形式のプロパティファイルは、`@PropertySource`の`factory`属性を利用することで値を参照できる。


## 参考
[baeldung SpringBootのYAMLファイルを使用した@PropertySource](https://www.baeldung.com/spring-yaml-propertysource)
[baeldung yaml形式での取得](https://www.baeldung.com/spring-yaml-inject-map)
[機能別 プロパティの取得方法](https://vist764.hatenablog.com/entry/2020/03/18/020836#value%E3%81%A7%E5%8F%96%E5%BE%97Spring%E6%A9%9F%E8%83%BD)


### 関連記事
[開発・本番用 環境別に取得する方法](https://dev.classmethod.jp/articles/spring-boot-config-yaml/)