8Kotlin in Action

注解与反射

元编程

注解反射序列化编译时处理

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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 声明注解

kotlin
// 基本注解
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 提供了一些元注解来修饰注解本身:

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 注解参数类型

注解参数的类型有限制:

kotlin
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 MyClass

2.1.4 注解使用-site 目标

当一个属性有多个可注解的位置(字段、getter、setter)时,需要指定注解的目标:

kotlin
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 注解处理器:

kotlin
// 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 设计,速度更快:

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 类引用

kotlin
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 函数引用

kotlin
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())    // 6

2.3.3 属性引用

kotlin
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))    // Alice

2.3.4 反射调用

kotlin
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 运行时类型信息

kotlin
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 序列化

kotlin
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 简单的路由注解

kotlin
@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 案例一:实现一个简单的依赖注入框架

kotlin
@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 案例二:实现一个编译时验证注解

kotlin
@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 案例三:使用反射实现对象深拷贝

kotlin
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 的自定义版本,支持指定替代方案和版本号。


8/8注解与反
0%