Spring Bootの機能紹介(@ConfigurationProperties)

# 概要

Spring Bootでは環境別に設定を切り替える仕組みとして`application.properties`や`application.yml`ファイルを使えます。

Webアプリのポート番号やDB接続情報の設定で使うことが多いと思います。

これらで設定された値をアプリケーション内で利用する方法の1つをメモをかねて紹介します。

# `@Value`アノテーション

これは、SpringFrameworkに備わった機能で、Spring Bootでなくても使えることもあり、よく使われていると思います。

次のような`application.properties`を用意して

```properties
app.name=exampleApplication
app.version=1.0.0.RELEASE
```
次のコンポーネントで利用できます。

```java
@Component
public class SomeComponent {

    private static final Logger logger = LoggerFactory.getLogger(com.example.SomeComponent.class);

    @Value("${app.name}")
    private String applicationName;

    @Value("${app.version}")
    private String applicationVersion;

    public void logName() {
        logger.info("app-name = {}:{}", this.applicationName, this.applicationVersion);
    }

}
```

ちなみに、SpringFrameworkの機能は`@Value`でのプロパティの読み込みまでで、`application.properties`ファイルの設定値を自動的に読み込むのはSpring Bootの機能です。

# `@ConfigurationProperties`アノテーション
ところで、適切にキー名が考慮されていれば次のように、キー名とフィールド名が同じ、ということも良くあるのではないでしょうか。そのようなときにはSpring Bootのプロパティ読み込みの仕組みである`@ConfigurationProperties`による読み込みも利用できます。

## 例

次のコードは`@Value`を使った(比較的)一般的なコードです。

```java
@Component
public class SomeComponent {

    @Value("${app.some-config.url}")
    private String url;

    @Value("${app.some-config.username}")
    private String username;

    @Value("${app.some-config.password}")
    private String password

    // ...

}
```

このような場合は`@ConfigurationProperties`を使って次のように書けます。

```java
@Component
@ConfigurationProperties(prefix = "app.some-config")
public class SomeComponent {

    private String url;

    private String username;

    private String password

    public String getUrl() {
        return this.url;
    }
    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return this.username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    // ...

}
```

なんだか、Getter/Setterのせいで後の方がコードは長くなってしまいましたが、これらはLombokを使ったり、IDEの自動生成機能でも生成できるので手間は後の例の方が少ないのではないでしょうか。

`@ConfigurationProperties`は、設定値の階層構造に着目しており、上記の例に加えて`app.some-config.paths.func1`のようなキーを読み込みたい場合は次のようにする必要があります。

```java
@Component
@ConfigurationProperties(prefix = "app.some-config")
public class SomeConponent {

    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}
public class FunctionPathsProperties {
    @Getter
    @Setter
    private String func1;
}
```

(長くなるのでLombokを使っています)

## 使用方法

`@ConfigurationProperties`アノテーションの付けられたBeanがSpringDIに登録されるとSetterを通じてプロパティの値が設定されます。従って、`@Value`では不要なGetter/Setterが必要になります。(一定の条件を満たすときはSetterが省略できますがGetterは省略できません)

また、設定値を保持するクラスをBeanとしてSpringDIに認識させる必要があります。
これは、次の3つの方法があります:
1. `@ConfigurationProperties`を付けたクラスに`@Component`を付けてBean登録する(上の例を参照)
2. `@ConfigurationProperties`を付けたクラスのインスタンスを`@Bean`アノテーションでBean登録する
3. `@ConfigurationProperties`を付けたクラスを、`@Configuration`クラスに`@EnableConfigurationProperties`アノテーションで指定してBeanに登録する

上記 2.の場合は次の通り

```java
/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    private SomeProps some;

    // ...

}

/**
 * java config
 */
@Configuration
public class OtherConfig {

    @Bean
    public SomeProps someConfig() {
        return new SomePropes();
    }

    // ...

}
```

また、3. の場合は次の通り

```java
/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    private SomeProps some;

    // ...

}

/**
 * java config
 */
@Configuration
@EnableConfigurationProperties({SomeProps.class})
public class OtherConfig {
    // ...
}
```

ところでこの例だと2.のパターンは3.の方が明らかに簡単なので使い道がなさそうです。
しかし、じつは2.のパターンのときは`@Bean`でBean登録する際に`@Bean`を付けたのとおなじメソッドに`@ConfigurationProperties`を付けることで`prefix`を上書きし、Beanをby-nameで`Autowire`、出来るようにしておくことで、「同じ構造の異なるprefixをもつプロパティ」を1つの`@ConfigurationProperties`クラスで読み込むことが出来ます:

```java
/**
 * 設定値クラス
 */
@ConfigurationPoperties(prefix = "app.some-config")
public SomeProps {
    @Getter
    @Setter
    private String url;

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password

    @Getter
    @Setter
    private FunctionPathsProperties paths

    // ...

}

/**
 * 設定値の利用クラス1(コンポーネント)
 */
@Component
public class SomeComponent {

    @Autowired
    @Qualifier("someProps")
    private SomeProps some;

    // ...

}

/**
 * 設定値の利用クラス2(コンポーネント)
 */
@Component
public class AnotherComponent {

    @Autowired
    @Qualifier("anotherProps")
    private SomeProps another;

    // ...

}

/**
 * java config
 */
@Configuration
public class OtherConfig {

    @Bean(name = "someProps")
    @ConfigurationProperties(prefix = "app.some-config")
    public SomeProps someConfig() {
        return new SomePropes();
    }

    // 構造は同じだが別の値
    @Bean(name = "anotherProps")
    @ConfigurationProperties(prefix = "app.another-config")
    public SomeProps anotherConfig() {
        return new SomePropes();
    }

    // ...

}
```

# Relaxed Binding

この記事を書くために[公式リファレンス](https://docs.spring.io/spring-boot/3.4/reference/features/external-config.html#features.external-config.typesafe-configuration-properties.relaxed-binding)を確認していて見つけた機能です。

プロパティのキー名とフィールド名がある程度違っていても一定のルールで調整してくれる(いわゆる「よきに計らってくれる」)機能です。

これは、プロパティファイルでは複数語のキーを設定する場合はハイフンで区切ったり(`kebab-case`)やアンダースコアで区切る(`snake_case`)事が多いですが、フィールド名は大文字小文字で区切りを表現する(camelCase)事が多い(`-`に至っては言語仕様で使えない!)ことも実装の動機になっていると思われます。

次に挙げるプロパティキー(代表的なもののみ挙げています)は、`@ConfigurationProperties`で読み込むときは同じプロパティとして`driverClassName`フィールドに読み込まれます:

- `driverClassName`
- `driver-class-name`
- `driver_class_name`
- `DRIVER_CLASS_NAME`

なおこの機能は[公式のリファレンス](https://docs.spring.io/spring-boot/3.4/reference/features/external-config.html#features.external-config.typesafe-configuration-properties.vs-value-annotation)にもあるように`@Value`だと一部制限があります[^1]。ただ、`@Value`アノテーションに与える参照式で`kebab-case`のみ使うように気を付ければ、同じように使えるようです。

:::note info
公式ドキュメントの例をまとめると、以下のようになるようです:
|  | プロパティ`demo.item-price` | プロパティ`demo.itemPrice` | 環境変数`DEMO_ITEMPRICE` |
|:-:|:-:|:-:|:-:|
| `@Value("demo.item-price")` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `@Value("demo.itemPrice")` | :x: | :white_check_mark: | :x: |
:::

[^1]: この記事の最初の執筆時点の最新版であるSpring Boot 2.0.3の時点において、`@Value`ではRelaxed bindingは利用できませんでした。いつの時点からかは詳しくわかりませんが、現行の3.4.3では本文の通り、一部制限付きながら利用できるようになりました。

# まとめ

`@Value`による読み込みも、SpringEL式が使えるなどの利点がありますが、階層構造を持ったひとまとまりのプロパティ群があるときは`@ConfigurationProperties`の利用も検討できると良いでしょう。

今回は適当な例が挙げられませんでしたが、本家のSpring Bootでは[DataSourceProperties](https://github.com/spring-projects/spring-boot/blob/3.4.x/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java)(`spring.datasource`の読み込みクラス)などに使われており、デフォルト値の設定などもこの中で行われています。

# 参考文献

[Spring Boot公式リファレンス](https://docs.spring.io/spring-boot/3.3/index.html)

---

継続的に一定の方に見ていただいているようで、とてもうれしく、励みになっています。ありがとうございます。

最新のSpring Boot 3.4.3のドキュメントを確認して、基本的には同じ内容で動作することを確認して、ドキュメントのリンク先などを修正しました。

使えなくなった機能などはなく、`@Value`アノテーションで、一部制限付きながらRelaxed bindingが利用できるようになったようでした。