プロパティファイルの値を期待する型のフィールドに代入するサンプルコード

# 概要

プロパティファイルの値を`@Value`アノテーションを使って代入するサンプルコードです。
この記事では扱っていませんが`@ConfigurationProperties`アノテーションを使ったConfigurationクラスでも同じ結果が得られます。

* 配列形式の値をList型へ
* 配列形式の値をSet型へ
* キー／バリュー形式の値をMap型へ
* 日付形式の値をLocalDateTime型へ
* ファイルパス形式の値をPath型へ
* 文字列の値をenum型へ

**環境**

* Windows 10 Professional
* Java 1.8.0_144
* Spring-Boot 1.5.4

**参考**

* [Part IV. Spring Boot features / 24. Externalized Configuration] (https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html)
* [Interface Converter\<S,T\>] (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/convert/converter/Converter.html)
* [Interface ConverterRegistry] (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/convert/converter/ConverterRegistry.html)
* [Interface ConditionalConverter] (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/convert/converter/ConditionalConverter.html)
* [Properties with Spring and Spring Boot] (http://www.baeldung.com/properties-with-spring)

# 配列形式の値をList型へ

## 数値型の配列を代入する

プロパティファイルでカンマ区切りの値を配列と呼ぶかは定かではないのですが、Spring Bootではカンマ区切りの値をコレクション型のフィールドへ代入することができます。

**プロパティファイル**

```text:properites
hoge.fuga = 100, 200, 300
```

もしくはプロパティ名に`[0]`のように添え字を付けることで配列として定義できます。

```text:properties
hoge.fuga[0]= 100
hoge.fuga[1]= 200
hoge.fuga[2]= 300
```

yml形式の場合は値の前に`-`を付けて定義します。

```yaml:yml
hoge:
  fuga:
    - 100
    - 200
    - 300
```

**コード**

```java
@Value("${hoge.fuga}")
private List<Integer> fuga;
```

## 文字列型の配列を代入する

**プロパティファイル**

```text:properites
hoge.fuga = りんご, みかん, なし
```

```yaml:yml
hoge:
  fuga:
    - りんご
    - みかん
    - なし
```

**コード**

```java
@Value("${hoge.fuga}")
private List<String> fuga;
```

# 配列形式の値をSet型へ

## 文字列型の配列を代入する

**プロパティファイル**

```text:properties
hoge.fuga = りんご, みかん, なし
```

ymlファイルの場合、配列をSet型フィールドへ代入できません。
下記のような配列を代入しようとすると例外がスローされます。

```yaml:yml
hoge:
  fuga:
    - りんご
    - みかん
    - なし
```

しかし、propertiesファイルと同様にカンマ区切りにした場合は代入することが可能です。

```yaml:yml
hoge:
  fuga: りんご, みかん, なし
```

**コード**

```java
Value("${hoge.fuga}")
private Set<String> fuga;
```

# キー／バリュー形式の値をMap型へ

## キーが文字列型のMapへ代入する

**プロパティファイル**

```text:properites
hoge.fuga.key1 = val1
hoge.fuga.key2 = val2
```

```yaml:yml
hoge:
  fuga:
    key1: val1
    key2: val2
```

**コード**

```java
@Value("${hoge.fuga}")
private Map<String, String> fuga;
```

## キーが数値型のMapへ代入する

**プロパティファイル**

```text:properties
hoge.fuga.101 = 123456789
hoge.fuga.201 = 456789012
```

```yaml:yml
hoge:
  fuga:
    101: 123456789
    201: 456789012
```

**コード**

```java
@Value("${hoge.fuga}")
private Map<Integer, Long> fuga;
```

# 日付形式の値をLocalDateTime型へ

LocalDate / LocalDateTime型へ代入する場合は、設定クラスに下記のようなFormatterを実装しConversionServiceに登録します。

```java
@Bean
public ConversionService conversionService() {
  Set<FormatterRegistrar> registrars = new HashSet<>();
  registrars.add(dateTimeFormatterRegistrar());

  FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
  factory.setFormatterRegistrars(registrars);
  factory.afterPropertiesSet();
  return factory.getObject();
}

private FormatterRegistrar dateTimeFormatterRegistrar() {
  DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
  registrar.setDateTimeFormatter(dateTimeFormatterFactory());
  registrar.setUseIsoFormat(true);
  return registrar;
}

// Formatterの実装
private DateTimeFormatter dateTimeFormatterFactory() {
  DateTimeFormatterFactoryBean factory = new DateTimeFormatterFactoryBean();
  factory.setPattern("yyyy-MM-dd HH:mm:ss");
  factory.setTimeZone(TimeZone.getDefault());
  factory.afterPropertiesSet();
  return factory.getObject();
}
```

## LocalDateTime型へ代入する

**プロパティファイル**

```text:properites
hoge.fuga = 2017-08-01 23:59:59
```

```yaml:yml
hoge:
  fuga: 2017-08-01 23:59:59
```

**コード**

```java
@Value("${hoge.fuga}")
private LocalDateTime fuga;
```

## LocalDate型へ代入する

**プロパティファイル**

```text:properites
hoge.fuga = 2017-08-01
```

```yaml:yml
hoge:
  fuga: 2017-08-01
```

**コード**

```java
@Value("${hoge.fuga}")
private LocalDate fuga;
```

# ファイルパス形式の値をPath型へ

コンバーターなどの実装は不要です。サンプルとして絶対パス、相対パスを挙げていますがこれらの扱いに違いはありません。

## 相対パスの値を代入する

**プロパティファイル**

プロパティファイルの場合、パス区切り文字をエスケープしないとエラーになります。(windows環境の場合、Unix/Linux環境は未確認です)

```text:properites
hoge.fuga = dir1\\fuga.txt
```

ymlの場合は、パス区切り文字のエスケープは不要です。

```yaml:yml
hoge:
  fuga: dir1\fuga.txt
```

**コード**

```java
@Value("${hoge.fuga}")
private Path fuga;
```

## 絶対パスの値を代入する

**プロパティファイル**

```text:properites
hoge.fuga = D:\\dir1\\dir2\\fuga.txt
```

```yaml:yml
hoge:
  fuga: D:\dir1\dir2\fuga.txt
```

**コード**

```java
@Value("${hoge.fuga}")
private Path path;
```

# 文字列の値をenum型へ

プロパティの値をenum型へ代入するサンプルです。

## 文字列から解決したenumを代入する

プロパティファイルに記述した文字列から対応するenumを解決して代入します。
コンバーターなどの実装は不要です。

**プロパティファイル**

```text:properites
hoge.fuga = Gold
```

```yaml:yml
hoge:
  fuga: Gold
```

**enum定義**

```java:enum
public enum Material {
    Bronze,
    Sliver,
    Gold
    ;
}
```

**コード**

```java
Value("${hoge.fuga}")
private Material fuga;
```

## 列挙子のフィールド値から解決したenumを代入する

プロパティファイルに記述した列挙子のフィールド値（この例ではlabelというフィールド）から対応するenumを解決して代入します。
この場合は、設定クラスに下記のようなConverterを実装しConversionServiceに登録します。

```java
@Bean
public ConversionService conversionService() {
  Set<Converter<?, ?>> converters = new HashSet<>();
  converters.add(new StringToMaterialConverter());

  FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
  factory.setConverters(converters);

  factory.afterPropertiesSet();
  return factory.getObject();
}
```

```java
import com.example.lib.constants.Material;
import org.springframework.core.convert.converter.Converter;

public class StringToMaterialConverter implements Converter<String, Material> {

  @Override
  public Material convert(String value) {
    return Material.lookup(value);
  }

}
```

**プロパティファイル**

```text:properites
hoge.fuga = 金
```

```yaml:yml
hoge:
  fuga: 金
```

**enum定義**

```java:enum
public enum Material {
    Bronze("銅"),
    Sliver("銀"),
    Gold("金")
    ;

    Material(String label) {
        this.label = label;
    }

    private String label;

    public String getLabel() {
        return this.label;
    }

    public static Material lookup(String label) {
        return Arrays.stream(Material.values())
                .filter(material -> material.getLabel().equals(label))
                .findFirst().orElseThrow(() -> new RuntimeException("unknown label : " + label));
    }

}
```

**コード**

```java
Value("${hoge.fuga}")
private Material fuga;
```