結論
- companion object に、String を引数に取るファクトリーメソッドを定義すれば良い
- メソッド名は valueOf, of, from のいずれか
- メソッドには
@JvmStatic
をつける
検証環境
Spring 6.1.6
Spring Boot 3.2.5
Kotlin 1.9.23
やりたいこと
構造を持ったデータクラスをパス変数として受け取りたい。
@RestController
class ClassroomController {
@GetMapping("/classrooms/{id}")
fun read(@PathVariable id: ClassroomId): Classroom {
// 略
}
}
data class ClassroomId(val grade: Int, val group: String)
/classrooms/3-A
に対するリクエストでは ClassroomId(3, "A")
を引数で受け取るイメージです。Custom Editor などで対応可能ではあるものの面倒なので、なにか簡単な方法はないものかと調べていたところ、思いの外簡単に実現できることを知りました。
PathVariable の変換ルール
パス変数は @PathVariable
を付けた引数で受け取れますが、大半の型変換は Spring がよしなにやってくれます。
引数の型に対応する Converter が存在しない場合、Spring Boot のデフォルトでは ObjectToObjectConverter
で変換を行っているようです。
このクラスでは次の順に変換を試みます。
- 変換後のクラスに存在するファクトリーメソッド(
String
を引数に取るもの)
※メソッド名はvalueOf
、of
、from
のいずれか - 変換後のクラスのコンストラクタ(
String
を引数に取るもの)
Kotlin の場合の注意
ファクトリーメソッドを追加すれば良いのですが、次のようにしても変換されません。
data class ClassroomId(val grade: Int, val group: String) {
companion object {
/**
* [String] から [ClassroomId] を生成するファクトリーメソッド。
*
* たとえば "3-A" のような値を受け取る。
*/
fun valueOf(stringValue: String): ClassroomId {
val (grade, group) = stringValue.split("-")
return ClassroomId(grade.toInt(), group)
}
}
}
companion object で定義した valueOf
メソッドを Java から呼び出すには次のようになります。
ClassroomId.Companion.valueOf("3-A")
Spring は次のようなファクトリーメソッドを探すので、見つけてもらえないのですね。
ClassroomId.valueOf("3-A")
この問題に対処するには @JvmStatic
を使います。valueOf
メソッドにアノテーションをつければ、ClassroomId
の static メソッドも生成されるので、Java からは以下どちらの方法でも扱えるようになります。
ClassroomId.valueOf("3-A")
ClassroomId.Companion.valueOf("3-A")
バリデーション
data class にマッピングできるともう一つ嬉しいことがあります。
ファクトリーメソッドで分解しておけば、data class の各プロパティに対して Bean Validation が適用できます。たとえば1年A組〜3年E組以外をエラーにするなら次のようになります。
data class ClassroomId(
@field:Min(1)
@field:Max(3)
val grade: Int,
@field:Pattern(regexp = "[A-E]")
val group: String
) {
companion object {
@JvmStatic
fun valueOf(stringValue: String): ClassroomId {
val (grade, group) = stringValue.split("-")
return ClassroomId(grade.toInt(), group)
}
}
}
参考
ソースコード | 説明 |
---|---|
DefaultConversionService.java | デフォルトで利用される型変換サービス。ここで登録されている Converter はデフォルトで利用可能。 |
ObjectToObjectConverter.java#L184-L200 |
ObjectToObjectConverter の中で変換に使うファクトリーメソッドを探している部分。 |
DefaultConversionServiceTests.java#L800-L809 |
valueOf で変換されることのテスト。 |