基于反射的验证与映射
使用注解驱动的反射构建简单的验证器或对象映射器。
基于反射的验证与映射 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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 将对象映射为键值映射
另一种反射模式使用 @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 }
}
}组合验证与映射
您可以在同一属性上叠加多个注解,并分别处理每个注解。您可以先验证再映射,也可以通过收集每种注解类型,在一次遍历中完成两者:
data class Product(
@Validate(minLength = 1) @Column("product_name") val name: String,
@Column("price") val price: Double
)局限性
基于反射的验证功能强大,但也有一些权衡:它会增加运行时开销,依赖注解保留策略,并且无法在编译时捕获错误。对于性能关键型或需要编译时安全性的验证,请改用 KSP 生成的代码。
何时使用此模式
基于反射的验证非常适合必须处理任意用户定义类的框架代码,例如对象关系映射器、序列化器、表单验证器和配置绑定器。对于由应用程序自行管理类型的代码,应优先采用显式验证,以获得更高的速度和更清晰的逻辑。
快速检查
验证注解必须使用哪种注解保留策略,才能在运行时读取?
回顾:基于反射的验证与映射
要点:
- 声明在运行时保留且以属性为目标的注解
- 使用
declaredMemberProperties+findAnnotation驱动验证或映射 - 按
KClass缓存结果,避免重复的反射开销 - 在属性上叠加多个注解,以实现组合行为
- 对于性能关键路径,考虑使用 KSP 作为编译时替代方案
常见问题解答
「基于反射的验证与映射」课时是免费的吗?
是的 — 「基于反射的验证与映射」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。
「基于反射的验证与映射」这节课中我会学到什么?
使用注解驱动的反射构建简单的验证器或对象映射器。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Kotlin Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「基于反射的验证与映射」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Kotlin Academy 课中编写并运行代码吗?
能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。