Spring Boot 2.2系で@ConfigurationPropertiesを使う

[Spring Boot 2.2.0のリリース](https://spring.io/blog/2019/10/16/spring-boot-2-2-0#immutable-code-configurationproperties-code-binding)でSpring Boot 2.2.系から`@ConfigurationProperties`が使いやすくなったので紹介します。

## Change Log
**2019.11.07** 
Spring Boot 2.2.1の変更を追記しました。また一部文言を修正しました。

## TL;DR
- `@ConstructorBinding`を付与することで、`@ConfigurationProperties`が付与されたクラスにセッターを定義せずに済むようになった(Kotlinの場合`lateinit var`を使わずにコンストラクタにプロパティを定義できるようになった)

- ~~`@EnableConfigurationProperties`や`@Component`を使わずに、`@ConfigurationProperties`が付与されたクラスをスキャンできるようになった~~  
**2019.11.07 追記:**
Spring Boot 2.2.1で`@ConfigurationProperties`がデフォルトでスキャンされなくなる修正が入りました。 
https://github.com/spring-projects/spring-boot/issues/18674
Spring Boot 2.2.0で`@EnableConfigurationProperties`や`@Component`を使わずに`@ConfigurationProperties`を利用しているアプリケーションを2.2.1に更新する場合は修正が必要です。修正方法は後述します。

## 前提
以下の設定ファイルに書かれた情報を出力するサンプルコードをKotlinを使ってSpring Boot 2.1系と 2.2系で実装します。

```application.properties
sample.foo=FOO
sample.bar=BAR
```


## Spring Boot 2.1系の場合
環境: Spring Boot 2.1.10.BUILD-SNAPSHOT

```kotlin
package com.dais39.springboot21kt

import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.runApplication
import org.springframework.stereotype.Component

@SpringBootApplication
// @EnableConfigurationProperties(SampleConfig::class)
class Spring21KtApplication

fun main(args: Array<String>) {
    runApplication<Spring21KtApplication>(*args)
}

@Component //@EnableConfigurationPropertiesを使う場合は不要
@ConfigurationProperties(prefix = "sample")
class SampleConfig {

    lateinit var foo: String

    lateinit var bar: String
}

@Component
class SampleLauncher(private val config: SampleConfig) : CommandLineRunner {
    override fun run(vararg args: String?) {
        println(config.foo)
        println(config.bar)
    }
}

```
Spring Boot2.1系までは、`@ConfigurationProperties`を付与したクラスをスキャンするために、`@Component`を使うか、`@EnableConfigurationProperties`を使う必要があります。

また、設定ファイルの値をプロパティにバインドするためにセッターを利用する必要があるため、Kotlinでは`lateinit var`を使ってプロパティを定義する必要があります。

## Spring Boot 2.2系の場合
Spring Boot 2.2系では、`@ConfigurationProperties`を付与したクラスに`@ConstructorBinding`を付与することで、設定ファイルの値を、コンストラクタを使ってプロパティにバインドできるので、Javaではセッターを用意する必要がなくなり、Kotlinでは`lateinit var`を使わずにプロパティを定義できます。

また、Spring Boot 2.2.0 と Spring Boot 2.2.1 では一部挙動が異なるので注意が必要です。

### Spring Boot 2.2.0の場合

```kotlin
package com.dais39.springboot22kt

import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.boot.runApplication
import org.springframework.stereotype.Component

@SpringBootApplication
class Springboot22KtApplication

fun main(args: Array<String>) {
    runApplication<Springboot22KtApplication>(*args)
}

@ConstructorBinding
@ConfigurationProperties(prefix = "sample")
class SampleConfig(
    val foo: String,
    val bar: String
)

@Component
class SampleLauncher(private val config: SampleConfig) : CommandLineRunner {

    override fun run(vararg args: String?) {

        println(config.foo)
        println(config.bar)
    }
}

```
Spring Boot 2.2.0では、`@SpringBootApplication`を使ってアプリケーションを起動している場合は、自動で`@ConfigurationProperties`を付与したクラスがスキャンされるため、`@EnableConfigurationProperties`をJavaConfigに付与したり、`@ConfigurationProperties`を付与したクラスに`@Component`を付与する必要がありません。

### Spring Boot 2.2.1の場合
```kotlin

package com.dais39.springboot221kt

import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.context.properties.ConstructorBinding
import org.springframework.boot.runApplication
import org.springframework.stereotype.Component

@SpringBootApplication
@ConfigurationPropertiesScan // もしくは @EnableConfigurationProperties(SampleConfig::class)
class Springboot221KtApplication

fun main(args: Array<String>) {
    runApplication<Springboot221KtApplication>(*args)
}

@ConstructorBinding
@ConfigurationProperties(prefix = "sample")
class SampleConfig(
    val foo: String,
    val bar: String
)

@Component
class SampleLauncher(private val config: SampleConfig) : CommandLineRunner {

    override fun run(vararg args: String?) {

        println(config.foo)
        println(config.bar)
    }
}
```
Spring Boot 2.2.1で`@ConfigurationProperties`が付与されたクラスをデフォルトでスキャンしないよう修正されました。
`@EnableConfigurationProperties`もしくは`@ConfigurationPropertiesScan`をJavaConfigに付与することで`@ConfigurationProperties`を付与したクラスをスキャンできるようになります。もしくは`@ConstructorBinding`を利用していないクラスであれば`@ConfigurationProperties`を付与したクラスに`@Component`を付与するだけで自動でスキャンされるようになります。

## 参考
Spring Boot 2.2 Release Notes, `@ConfigurationProperties` scanning
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes#configurationproperties-scanning

Spring Boot 2.2 Release Notes, Immutable `@ConfigurationProperties` binding
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes#immutable-configurationproperties-binding

Spring Boot Reference, 2.8. Type-safe Configuration Properties
https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config-typesafe-configuration-properties

Enabling configuration properties scanning by default prevents conditional registration of `@ConfigurationProperties-annoted` types that are found by scanning
https://github.com/spring-projects/spring-boot/issues/18674