Spring Bootのコンストラクタバインディング完全解説!初心者でもわかるイミュータブル設定クラスの作り方
生徒
「Spring Bootで設定クラスを不変(イミュータブル)にしたいんですが、どうすればいいですか?」
先生
「その場合は、@ConfigurationPropertiesと一緒にコンストラクタバインディングを使うのがベストです。」
生徒
「コンストラクタバインディングってなんですか?普通のバインディングと何が違うんですか?」
先生
「それでは、イミュータブルな設定クラスを作る方法を、コンストラクタバインディングの仕組みと一緒に解説していきましょう。」
1. コンストラクタバインディングとは?
Spring Bootで設定クラスを定義するとき、多くの場合は@ConfigurationPropertiesと@Componentを組み合わせて、セッターで値を設定します。しかし、それではクラスのプロパティが変更可能(ミュータブル)になります。
コンストラクタバインディングは、finalフィールドとコンストラクタを使って、不変(イミュータブル)な設定クラスを作るための仕組みです。
Spring Boot 2.2以降で正式にサポートされており、設計上の安全性が向上します。
2. コンストラクタバインディングのメリット
コンストラクタバインディングを使う主なメリットは以下のとおりです。
- クラスのプロパティが
finalで定義されるため、再代入を防げる - 不変オブジェクトになることで、スレッドセーフになる
- コンパイル時に
初期化漏れが検出できる - テストやDIがしやすく、保守性が高い
つまり、より堅牢で信頼性の高い設定クラスが作れます。
3. コンストラクタバインディングの基本構成
まずは、application.ymlに次のような設定を書きます。
sample:
service:
name: ExampleService
port: 8080
次にJavaクラスを定義します。
@ConfigurationProperties(prefix = "sample.service")
@ConstructorBinding
public class ServiceProperties {
private final String name;
private final int port;
public ServiceProperties(String name, int port) {
this.name = name;
this.port = port;
}
public String getName() {
return name;
}
public int getPort() {
return port;
}
}
このようにすれば、プロパティの変更を禁止したイミュータブルな設定クラスになります。
4. @EnableConfigurationPropertiesで登録する
コンストラクタバインディングを使うクラスは、@Componentを付けずに、@EnableConfigurationPropertiesで登録します。
@Configuration
@EnableConfigurationProperties(ServiceProperties.class)
public class Config {
}
この方法により、Spring Bootの自動設定に依存せずに、明示的に設定クラスをBean登録できます。
5. Spring Bootでの利用例
設定クラスを実際のコントローラーなどで利用するには、コンストラクタインジェクションを使います。
@RestController
public class SampleController {
private final ServiceProperties serviceProperties;
public SampleController(ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
@GetMapping("/info")
public String info() {
return serviceProperties.getName() + " - " + serviceProperties.getPort();
}
}
このようにすれば、設定ファイルの内容をクラスにバインドし、Webアプリケーションから動的に利用することができます。
6. @ConstructorBindingの注意点
@ConstructorBindingを使用するには、以下の条件を満たす必要があります。
- Javaクラスは
@Componentを付けない - 明示的に
@EnableConfigurationPropertiesでBean登録する - Spring Bootのバージョンが2.2以降であること
また、インナークラスやネストした設定を使う場合も、すべてのクラスで同様にfinalとコンストラクタを使う必要があります。
7. ネスト構造でのコンストラクタバインディング
設定ファイルにネストされた構造がある場合も、同様にクラスを分けてコンストラクタバインディングを適用できます。
sample:
service:
name: NestedService
security:
token: abc123
expire: 60
public class Security {
private final String token;
private final int expire;
public Security(String token, int expire) {
this.token = token;
this.expire = expire;
}
// getter
}
@ConfigurationProperties(prefix = "sample.service")
@ConstructorBinding
public class ServiceProperties {
private final String name;
private final Security security;
public ServiceProperties(String name, Security security) {
this.name = name;
this.security = security;
}
// getter
}
このようにネストしていても、イミュータブルで保守性の高い設計が可能です。
8. バリデーションとの組み合わせ
@Validatedはコンストラクタバインディングと同時には使用できません。ただし、JSR-303のバリデーションアノテーションを使った検証は可能です。
検証のロジックを自前で作りたい場合は、バインディング後に別途チェック処理を実装するか、ファクトリーメソッドで制御するのも一つの方法です。
9. レコード(record)との組み合わせ
Java 14以降ではrecordを使って、もっと簡潔にイミュータブルな設定クラスを作成できます。
@ConfigurationProperties(prefix = "sample.service")
@ConstructorBinding
public record ServiceProperties(String name, int port) {
}
レコード型は自動的にコンストラクタを持つため、非常に相性が良く、よりシンプルでモダンな記述が可能になります。