リフレクションベースのバリデーションとマッピング
アノテーション駆動のリフレクションを使い、シンプルなバリデーターまたはオブジェクトマッパーを構築します。
「リフレクションベースのバリデーションとマッピング」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。
このパターンで解決できること
リフレクションベースのバリデーションでは、実行時にクラスのプロパティからアノテーションを読み取り、各フィールドを手動でチェックすることなくバリデーションルールを自動的に適用します。同じ技術は、オブジェクトとデータベースの行を相互変換するORMマッパーにも利用されています。
Validateアノテーション
実行時まで保持され、プロパティを対象とするアノテーションを定義します。
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Validate(
val minLength: Int = 0,
val maxLength: Int = Int.MAX_VALUE,
val pattern: String = ""
)データクラスへのアノテーション付与
データクラスのプロパティに@Validateを付与します。バリデータは実行時にこれらを検査します。
data class UserForm(
@Validate(minLength = 2, maxLength = 50) val name: String,
@Validate(pattern = "^[\\w.]+@[\\w]+\\.[a-z]{2,}$") val email: String,
@Validate(minLength = 8) val password: String
)バリデータ関数
KClass.declaredMemberPropertiesを使ってプロパティを反復処理し、アノテーションを見つけてエラーを収集します。
fun validate(obj: Any): List<String> {
val errors = mutableListOf<String>()
for (prop in obj::class.declaredMemberProperties) {
val ann = prop.findAnnotation<Validate>() ?: continue
val value = (prop as KProperty1<Any, *>).get(obj)?.toString() ?: ""
if (value.length < ann.minLength) errors += "${prop.name}: too short"
if (value.length > ann.maxLength) errors += "${prop.name}: too long"
if (ann.pattern.isNotEmpty() && !value.matches(Regex(ann.pattern)))
errors += "${prop.name}: invalid format"
}
return errors
}バリデータの実行
アノテーションが付与された任意のオブジェクトをvalidate()に渡します。この関数はエラーメッセージのリストを返し、空のリストは有効であることを示します。
val form = UserForm("A", "not-an-email", "short")
val errors = validate(form)
errors.forEach(::println)
// name: too short
// email: invalid format
// password: too short@ColumnによるオブジェクトからMapへのマッピング
別のリフレクションパターンでは、@Columnアノテーションを使ってカラム名を指定し、オブジェクトをMapにマッピングします。
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Column(val name: String)
fun toMap(obj: Any): Map<String, Any?> =
obj::class.declaredMemberProperties
.mapNotNull { prop ->
val col = prop.findAnnotation<Column>() ?: return@mapNotNull null
col.name to (prop as KProperty1<Any, *>).get(obj)
}.toMap()マッパーの使用
データクラスにアノテーションを付与し、toMap()を呼び出すと、INSERT文にそのまま使える、カラムをキーとするマップを取得できます。
data class User(
@Column("user_name") val name: String,
@Column("user_email") val email: String
)
val user = User("Alice", "alice@example.com")
println(toMap(user))
// {user_name=Alice, user_email=alice@example.com}リフレクション結果のキャッシュ
リフレクションによるスキャンにはコストがかかります。KClassをキーとするConcurrentHashMapを使い、プロパティとアノテーションの対応関係を起動時にキャッシュします。
val cache = ConcurrentHashMap<KClass<*>, List<Pair<KProperty1<Any,*>, Column>>>()
fun columnsOf(klass: KClass<*>) = cache.getOrPut(klass) {
klass.declaredMemberProperties
.mapNotNull { p ->
p.findAnnotation<Column>()?.let { (p as KProperty1<Any,*>) to it }
}
}バリデーションとマッピングの組み合わせ
1つのプロパティに複数のアノテーションを重ねて付与し、それぞれを独立して処理できます。まずバリデーションを行ってからマッピングすることも、各アノテーション型を収集して1回の走査で両方を行うこともできます。
data class Product(
@Validate(minLength = 1) @Column("product_name") val name: String,
@Column("price") val price: Double
)制限事項
リフレクションベースのバリデーションは強力ですが、トレードオフがあります。実行時のオーバーヘッドが増え、アノテーションの保持設定に依存し、コンパイル時にはエラーを検出できません。パフォーマンスが重要な場合やコンパイル時の安全性が必要な場合は、KSPで生成したコードを検討してください。
このパターンを使う場面
リフレクションベースのバリデーションは、ORM、シリアライザ、フォームバリデータ、設定バインダーなど、任意のユーザー定義クラスを扱う必要があるフレームワークコードで特に有効です。型を自分で管理しているアプリケーションコードでは、速度とわかりやすさのために明示的なバリデーションを優先してください。
確認問題
バリデーション用アノテーションを実行時に読み取るには、どのアノテーション保持ポリシーが必要ですか。
まとめ:リフレクションベースのバリデーションとマッピング
重要なポイント:
- 実行時まで保持され、プロパティを対象とするアノテーションを宣言します
declaredMemberPropertiesとfindAnnotationを使ってバリデーションやマッピングを行います- リフレクションのオーバーヘッドを繰り返し発生させないよう、
KClassごとに結果をキャッシュします - 1つのプロパティに複数のアノテーションを重ねて付与し、複合的な動作を実現します
- パフォーマンスが重要な処理では、コンパイル時の代替手段としてKSPを検討します
よくある質問
「リフレクションベースのバリデーションとマッピング」レッスンは無料ですか?
はい。「リフレクションベースのバリデーションとマッピング」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。
「リフレクションベースのバリデーションとマッピング」で何を学びますか?
アノテーション駆動のリフレクションを使い、シンプルなバリデーターまたはオブジェクトマッパーを構築します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Kotlin Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「リフレクションベースのバリデーションとマッピング」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このKotlin Academyレッスンでコードを書いて実行できますか?
はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Kotlin Reflection:KClass、KFunction、KProperty
- カスタムアノテーションの作成と対象指定
- 実行時にアノテーションを読み取る
- リフレクションベースのバリデーションとマッピング