第8章:注解与反射(Annotations and Reflection)
一、导读
1.1 本章学习目标
第8章是《Kotlin in Action》的最后一章,讲解 Kotlin 的元编程能力——注解和反射。注解让你能够在代码中添加元数据,反射让你能够在运行时检查和操作这些元数据。
本章的核心学习目标包括:
- 掌握注解的声明和使用:元注解、注解参数、注解目标
- 理解编译时注解处理:kapt 和 KSP
- 掌握 Kotlin 反射 API:
KClass、KFunction、KProperty - 学会使用反射进行序列化、依赖注入等高级操作
- 理解反射的性能开销和替代方案
1.2 为什么本章如此重要
注解和反射是现代框架的基石。Spring 的依赖注入、Jackson 的 JSON 序列化、JUnit 的测试发现——这些都依赖于注解和反射。理解它们的工作原理,不仅能让你更好地使用框架,还能让你创建自己的框架和工具。
Kotlin 的注解系统比 Java 的更强大——支持类构造器参数作为注解目标、支持默认参数值、支持 vararg 参数。Kotlin 的反射 API 也提供了比 Java 反射更类型安全的接口。
1.3 核心问题引导
Kotlin 的注解与 Java 的注解有什么区别?
注解的参数可以有哪些类型?如何提供默认值?
@field:、@get:、@set: 等注解使用-site 目标是什么意思?
Kotlin 反射 API 与 Java 反射 API 有什么区别?
反射的性能开销有多大?有哪些替代方案?
二、核心概念详解
2.1 注解基础
2.1.1 声明注解
// 基本注解
annotation class Deprecated(val message: String)
// 带参数的注解
annotation class JsonField(
val name: String,
val required: Boolean = false
)
// 使用
@JsonField(name = "user_name", required = true)
val userName: String = "Alice"2.1.2 元注解
Kotlin 提供了一些元注解来修饰注解本身:
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
annotation class Test(val priority: Int = 0)
// @Target: 注解可以应用的目标
// @Retention: 注解的保留策略
// - SOURCE: 只在源码中保留
// - BINARY: 在字节码中保留(默认)
// - RUNTIME: 在运行时保留(可通过反射访问)
// @Repeatable: 注解可以重复使用2.1.3 注解参数类型
注解参数的类型有限制:
annotation class Example(
val number: Int, // 基本类型
val text: String, // String
val clazz: KClass<*>, // KClass(对应 Java 的 Class)
val items: Array<String>, // 数组
val enum: MyEnum // 枚举
)
enum class MyEnum { A, B, C }
@Example(
number = 42,
text = "hello",
clazz = String::class,
items = ["a", "b", "c"],
enum = MyEnum.A
)
class MyClass2.1.4 注解使用-site 目标
当一个属性有多个可注解的位置(字段、getter、setter)时,需要指定注解的目标:
class User {
@field:JvmName("user_name") // 注解应用于字段
@get:Deprecated("Use newName") // 注解应用于 getter
var name: String = ""
@Inject // 默认应用于参数
constructor(@JsonProperty("id") val id: Long)
}
// 常见的注解目标
// @field: 注解应用于字段
// @get: 注解应用于 getter
// @set: 注解应用于 setter
// @param: 注解应用于构造函数参数
// @property: 注解应用于 Kotlin 属性
// @setparam: 注解应用于 setter 的参数
// @file: 注解应用于文件2.2 注解处理器
2.2.1 KAPT(Kotlin Annotation Processing Tool)
KAPT 允许在 Kotlin 项目中使用 Java 注解处理器:
// build.gradle.kts
plugins {
kotlin("kapt") version "1.9.0"
}
dependencies {
kapt("com.google.dagger:dagger-compiler:2.47")
}2.2.2 KSP(Kotlin Symbol Processing)
KSP 是 KAPT 的替代方案,专为 Kotlin 设计,速度更快:
// 自定义注解处理器
class MyProcessor : SymbolProcessor {
override fun process(resolver: Resolver): List<KSPAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(MyAnnotation::class.qualifiedName!!)
// 处理注解...
return emptyList()
}
}2.3 Kotlin 反射
2.3.1 类引用
val clazz = String::class // KClass<String>
val javaClass = String::class.java // Class<String>
// 获取类信息
val kClass = User::class
println(kClass.simpleName) // "User"
println(kClass.qualifiedName) // "com.example.User"2.3.2 函数引用
fun greet(name: String) = "Hello, $name!"
// 函数引用
val funcRef = ::greet
println(funcRef("Alice")) // Hello, Alice!
// 成员函数引用
val lengthRef = String::length
println(lengthRef("Kotlin")) // 6
// 绑定引用
val str = "Kotlin"
val boundRef = str::length
println(boundRef()) // 62.3.3 属性引用
var counter = 0
// 属性引用
val propRef = ::counter
println(propRef.get()) // 0
propRef.set(42)
println(counter) // 42
// 成员属性引用
class Person(val name: String, var age: Int)
val nameRef = Person::name
val person = Person("Alice", 30)
println(nameRef.get(person)) // Alice2.3.4 反射调用
import kotlin.reflect.full.*
class Calculator {
fun add(a: Int, b: Int): Int = a + b
fun multiply(a: Int, b: Int): Int = a * b
}
fun main() {
val calc = Calculator()
val kClass = Calculator::class
// 获取所有成员函数
kClass.memberFunctions.forEach { func ->
println("${func.name}: ${func.parameters}")
}
// 调用函数
val addFunc = kClass.memberFunctions.find { it.name == "add" }
val result = addFunc?.call(calc, 3, 5)
println(result) // 8
}2.3.5 运行时类型信息
import kotlin.reflect.full.createInstance
class ServiceA {
fun execute() = println("ServiceA executing")
}
class ServiceB {
fun execute() = println("ServiceB executing")
}
fun main() {
val className = "ServiceA"
val kClass = when (className) {
"ServiceA" -> ServiceA::class
"ServiceB" -> ServiceB::class
else -> throw IllegalArgumentException("Unknown service: $className")
}
val instance = kClass.createInstance()
when (instance) {
is ServiceA -> instance.execute()
is ServiceB -> instance.execute()
}
}2.4 注解与反射的实际应用
2.4.1 简单的 JSON 序列化
annotation class JsonField(val name: String)
data class User(
@JsonField("user_name") val name: String,
@JsonField("user_age") val age: Int,
@JsonField("user_email") val email: String
)
fun toJson(obj: Any): String {
val kClass = obj::class
val fields = mutableMapOf<String, Any?>()
for (prop in kClass.memberProperties) {
val annotation = prop.annotations.filterIsInstance<JsonField>().firstOrNull()
val key = annotation?.name ?: prop.name
val value = prop.getter.call(obj)
fields[key] = value
}
return fields.entries.joinToString(", ") { (k, v) ->
"\"$k\": ${when (v) {
is String -> "\"$v\""
null -> "null"
else -> v.toString()
}}"
}.let { "{$it}" }
}
fun main() {
val user = User("Alice", 30, "alice@example.com")
println(toJson(user))
// {"user_name": "Alice", "user_age": 30, "user_email": "alice@example.com"}
}2.4.2 简单的路由注解
@Target(AnnotationTarget.FUNCTION)
annotation class Route(val path: String, val method: String = "GET")
class UserController {
@Route("/users")
fun listUsers(): String = "User list"
@Route("/users/{id}", method = "GET")
fun getUser(id: String): String = "User $id"
@Route("/users", method = "POST")
fun createUser(): String = "User created"
}
fun registerRoutes(controller: Any) {
val kClass = controller::class
for (func in kClass.memberFunctions) {
val route = func.annotations.filterIsInstance<Route>().firstOrNull()
if (route != null) {
println("Registering: ${route.method} ${route.path} -> ${func.name}")
}
}
}
fun main() {
registerRoutes(UserController())
}三、实战案例
3.1 案例一:实现一个简单的依赖注入框架
@Target(AnnotationTarget.CLASS)
annotation class Singleton
@Target(AnnotationTarget.CONSTRUCTOR)
annotation class Inject
class SimpleDI {
private val instances = mutableMapOf<String, Any>()
private val factories = mutableMapOf<String, () -> Any>()
inline fun <reified T : Any> register(noinline factory: () -> T) {
factories[T::class.qualifiedName!!] = factory
}
inline fun <reified T : Any> resolve(): T {
val key = T::class.qualifiedName!!
// 检查是否已有单例
instances[key]?.let { return it as T }
// 检查是否有工厂
val factory = factories[key]
if (factory != null) {
val instance = factory()
if (T::class.annotations.any { it is Singleton }) {
instances[key] = instance
}
return instance as T
}
// 尝试通过反射创建
val kClass = T::class
val constructor = kClass.constructors.firstOrNull()
?: throw IllegalArgumentException("No constructor for ${T::class}")
val params = constructor.parameters.map { param ->
resolve<Any>(param.type.classifier as KClass<Any>)
}
val instance = constructor.call(*params.toTypedArray())
if (T::class.annotations.any { it is Singleton }) {
instances[key] = instance
}
return instance
}
}3.2 案例二:实现一个编译时验证注解
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class NotBlank
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Range(val min: Int, val max: Int)
class Validator {
fun validate(obj: Any): List<String> {
val errors = mutableListOf<String>()
val kClass = obj::class
for (prop in kClass.memberProperties) {
val value = prop.getter.call(obj)
// 检查 @NotBlank
if (prop.annotations.any { it is NotBlank }) {
if (value is String && value.isBlank()) {
errors.add("${prop.name} must not be blank")
}
}
// 检查 @Range
val range = prop.annotations.filterIsInstance<Range>().firstOrNull()
if (range != null && value is Int) {
if (value < range.min || value > range.max) {
errors.add("${prop.name} must be between ${range.min} and ${range.max}")
}
}
}
return errors
}
}
data class CreateUserRequest(
@field:NotBlank val name: String,
@field:Range(1, 150) val age: Int,
@field:NotBlank val email: String
)
fun main() {
val request = CreateUserRequest("", 200, "test@example.com")
val errors = Validator().validate(request)
errors.forEach { println(it) }
}3.3 案例三:使用反射实现对象深拷贝
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
fun <T : Any> deepCopy(obj: T): T {
val kClass = obj::class
val constructor = kClass.primaryConstructor ?: throw IllegalArgumentException("No primary constructor")
val args = constructor.parameters.map { param ->
val value = kClass.memberProperties.find { it.name == param.name }?.getter?.call(obj)
when {
value == null -> null
value::class.isData -> deepCopy(value)
value is List<*> -> value.toList()
value is Map<*, *> -> value.toMap()
else -> value
}
}
return constructor.call(*args.toTypedArray())
}四、本章小结
第8章讲解了 Kotlin 的元编程能力:
注解:Kotlin 的注解支持类构造器参数、默认值、元注解(@Target、@Retention、@Repeatable)。注解使用-site 目标(@field:、@get: 等)控制注解的应用位置。
注解处理:KAPT 兼容 Java 注解处理器,KSP 是 Kotlin 原生的注解处理方案。
反射:Kotlin 反射 API 提供了 KClass、KFunction、KProperty 等类型安全的接口。
性能考量:反射有性能开销,在性能敏感的场景应考虑替代方案(如代码生成)。
五、思考与练习
思考题
Kotlin 的注解与 Java 的注解有哪些关键区别?@Retention 的默认值是什么?
反射的性能开销主要来自哪些方面?在什么场景下应该避免使用反射?
KAPT 和 KSP 有什么区别?为什么 Kotlin 社区在推动从 KAPT 迁移到 KSP?
注解处理器和运行时反射各自的适用场景是什么?
编程练习
实现一个 @NonNull 注解,在运行时检查被注解的属性是否被设置为 null。
使用反射实现一个简单的 CSV 序列化器,将数据类列表转换为 CSV 格式。
实现一个 @Deprecated 的自定义版本,支持指定替代方案和版本号。