跳到主要内容

Kotlin 注解

注解(Annotation)是一种将元数据附加到代码中的机制。通过注解,我们可以为类、函数、属性等元素添加额外信息,这些信息可以在编译时或运行时被工具和框架读取和使用。本章将详细介绍如何声明和使用注解。

注解基础概念

什么是注解?

注解是一种特殊的标记,可以被添加到代码的各个元素上(类、函数、属性、参数等)。注解本身不执行任何代码,但它们可以被编译器、注解处理器或运行时反射读取,从而实现特定的功能。

// 常见的内置注解示例
@Deprecated("使用 newFunction() 替代", ReplaceWith("newFunction()"))
fun oldFunction() { }

fun newFunction() { }

注解的作用

  1. 编译时检查:如 @Deprecated 警告、@Suppress 抑制警告
  2. 代码生成:通过注解处理器自动生成代码
  3. 运行时行为:框架通过反射读取注解来改变程序行为
  4. 文档生成:生成 API 文档时的额外信息

声明注解

基本语法

使用 annotation class 关键字声明注解:

// 最简单的注解
annotation class Review

// 使用注解
@Review
class CodeDocument {
@Review
fun process() { }
}

带参数的注解

注解可以有构造函数参数:

// 带参数的注解
annotation class Author(val name: String)

// 使用
@Author("张三")
class MyClass {
@Author("李四")
fun method() { }
}

// 多个参数
annotation class Api(
val version: String,
val deprecated: Boolean = false,
val description: String = ""
)

@Api(version = "2.0", description = "用户接口")
interface UserService {
@Api(version = "1.0", deprecated = true)
fun oldMethod()

@Api(version = "2.0")
fun newMethod()
}

注解参数的限制

注解参数的类型有限制,只能使用以下类型:

annotation class ValidTypes(
// 基本类型
val int: Int,
val long: Long,
val double: Double,
val float: Float,
val boolean: Boolean,
val byte: Byte,
val short: Short,
val char: Char,

// 字符串
val string: String,

// 枚举
val enum: Thread.State,

// 类引用
val klass: KClass<*>,

// 数组(元素类型也受限制)
val intArray: IntArray,
val stringArray: Array<String>,

// 其他注解
val anotherAnnotation: Author
)

// 使用示例
@ValidTypes(
int = 1,
long = 2L,
double = 3.0,
float = 4.0f,
boolean = true,
byte = 5,
short = 6,
char = 'A',
string = "hello",
enum = Thread.State.NEW,
klass = String::class,
intArray = [1, 2, 3],
stringArray = ["a", "b"],
anotherAnnotation = Author("张三")
)
class Example

注意:注解参数不能为可空类型(String? 不允许),因为 JVM 不支持将 null 存储为注解属性值。

// 错误:参数不能可空
// annotation class Bad(val name: String?)

元注解

元注解是用于注解注解本身的注解,用于控制注解的行为。

@Target - 指定目标

@Target 指定注解可以应用的目标元素:

// 只能用于类
@Target(AnnotationTarget.CLASS)
annotation class Entity

@Entity // 正确
class User
// @Entity fun wrong() = Unit // 编译错误

// 可以用于多个目标
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY
)
annotation class Documented

@Documented
class Service {
@Documented
val name: String = ""

@Documented
fun execute() { }
}

可用的目标类型

目标类型说明
CLASS类、接口、对象
ANNOTATION_CLASS注解类
PROPERTY属性(Kotlin 特有)
FIELD字段(Java 字段)
LOCAL_VARIABLE局部变量
VALUE_PARAMETER函数参数值
CONSTRUCTOR构造函数
FUNCTION函数
PROPERTY_GETTER属性 getter
PROPERTY_SETTER属性 setter
TYPE类型(泛型参数、返回类型等)
EXPRESSION表达式
FILE文件
TYPEALIAS类型别名
TYPE_PARAMETER类型参数

@Retention - 指定保留策略

@Retention 指定注解在什么阶段保留:

// SOURCE:仅在源代码保留,编译后丢弃
@Retention(AnnotationRetention.SOURCE)
annotation class SourceOnly

// BINARY:编译到 class 文件,但运行时不可见(默认)
@Retention(AnnotationRetention.BINARY)
annotation class BinaryRetention

// RUNTIME:编译到 class 文件,运行时可通过反射访问
@Retention(AnnotationRetention.RUNTIME)
annotation class RuntimeRetention

保留策略对比

策略源代码字节码运行时反射使用场景
SOURCE编译时检查、代码生成
BINARY编译时处理、工具分析
RUNTIME运行时框架、反射处理

@Repeatable - 可重复注解

@Repeatable 允许同一注解多次应用于同一元素:

@Repeatable
annotation class Tag(val name: String)

// 可以重复使用
@Tag("important")
@Tag("urgent")
@Tag("review")
class Task

// 编译器自动生成容器注解 Tag.Container

自定义容器注解:

@JvmRepeatable(Tags::class)
annotation class Label(val value: String)

// 自定义容器
annotation class Tags(val values: Array<Label>)

@Label("backend")
@Label("api")
@Label("v2")
class ApiController

@MustBeDocumented - 文档包含

@MustBeDocumented 指定注解应包含在生成的 API 文档中:

@MustBeDocumented
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Since(val version: String)

@Since("2.0")
class NewApi {
@Since("2.1")
fun latestFeature() { }
}

注解使用目标

当注解 Kotlin 代码时,有时需要明确指定注解应用于哪个 Java 元素。

使用目标语法

class Example(
@field:Ann val foo, // 注解字段
@get:Ann val bar, // 注解 getter
@param:Ann val quux // 注解构造函数参数
)

annotation class Ann

可用的使用目标

目标说明
file文件
propertyKotlin 属性(Kotlin 不会生成注解)
fieldJava 字段
get属性 getter
set属性 setter
receiver扩展函数/属性的接收者
param构造函数参数
setparamsetter 参数
delegate委托属性存储委托实例的字段

实际应用示例

// 文件级注解
@file:JvmName("StringUtils")

package com.example

// Java 互操作示例
import javax.inject.Inject

class UserPresenter {
// 注解字段
@field:Inject
lateinit var repository: UserRepository

// 注解 getter
@get:Synchronized
val count: Int = 0

// 注解 setter 参数
var name: String = ""
set(@setparam:NotEmpty value) {
field = value
}
}

// 注解扩展函数接收者
annotation class ReceiverAnn

fun @ReceiverAnn String.myExtension(): Int = this.length

annotation class NotEmpty
annotation class Synchronized
class UserRepository

默认目标选择

如果未指定使用目标,按以下顺序选择:

  1. param(构造函数参数)
  2. property(Kotlin 1.1+)
  3. field

内置注解

@Deprecated

标记已废弃的元素:

// 基本用法
@Deprecated("此方法已废弃")
fun oldMethod() { }

// 完整用法
@Deprecated(
message = "使用 newMethod() 替代",
replaceWith = ReplaceWith("newMethod()"),
level = DeprecationLevel.ERROR
)
fun deprecatedMethod() { }

fun newMethod() { }

fun main() {
// oldMethod() // 警告
// deprecatedMethod() // 编译错误
newMethod() // 正常
}

DeprecationLevel 级别:

  • WARNING:编译警告(默认)
  • ERROR:编译错误
  • HIDDEN:代码中不可见

@Suppress

抑制编译器警告:

// 抑制特定警告
@Suppress("UNCHECKED_CAST")
fun castExample() {
val list: List<Any> = listOf(1, 2, 3)
val intList = list as List<Int> // 未检查的转换
}

// 抑制多个警告
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
fun unusedExample(a: Int, b: String) {
val c = 100 // 未使用
}

常用抑制选项:

  • UNCHECKED_CAST:未检查的类型转换
  • UNUSED_PARAMETER:未使用的参数
  • UNUSED_VARIABLE:未使用的变量
  • DEPRECATION:废弃 API 使用
  • NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS:Java 空安全警告

@JvmStatic

为伴生对象方法生成静态方法:

class Utils {
companion object {
@JvmStatic
fun staticMethod() = "静态方法"

fun normalMethod() = "普通方法"
}
}

// Java 调用:
// Utils.staticMethod() // 直接调用
// Utils.Companion.normalMethod() // 需要通过 Companion

@JvmField

暴露属性为公共字段:

class Config {
companion object {
@JvmField
val VERSION = "1.0"

// 普通属性会生成 getter
val NAME = "MyApp"
}
}

// Java 调用:
// Config.VERSION // 直接访问字段
// Config.getNAME() // 通过 getter

@JvmName

指定 Java 中的名称:

@file:JvmName("StringUtils")  // 文件类名

// 函数名
@JvmName("addPrefix")
fun String.prefix(p: String) = "$p$this"

// Kotlin 调用: "hello".prefix(">> ")
// Java 调用: StringUtils.addPrefix("hello", ">> ")

@JvmOverloads

生成重载方法:

class Button(
@JvmField val text: String,
val width: Int = 100,
val height: Int = 50
) {
// 生成 4 个构造函数:
// Button(text)
// Button(text, width)
// Button(text, width, height)
}

@JvmOverloads
fun createUser(
name: String,
age: Int = 0,
email: String = ""
): User {
return User(name, age, email)
}

data class User(val name: String, val age: Int, val email: String)

// Java 调用:
// createUser("张三")
// createUser("张三", 25)
// createUser("张三", 25, "[email protected]")

@Throws

声明函数可能抛出的异常(用于 Java 互操作):

@Throws(IOException::class)
fun readFile(path: String): String {
return java.io.File(path).readText()
}

// Java 调用:
// try {
// readFile("test.txt");
// } catch (IOException e) {
// // 处理异常
// }

@Volatile

将属性标记为易变的(线程可见性):

class SharedResource {
@Volatile
var running = true

fun stop() {
running = false
}
}

@Synchronized

同步方法:

class Counter {
private var count = 0

@Synchronized
fun increment(): Int {
return ++count
}

@Synchronized
fun decrement(): Int {
return --count
}
}

注解与反射

使用反射在运行时读取注解需要将注解的 @Retention 设置为 RUNTIME

读取类注解

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Table(val name: String)

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Column(val name: String = "", val primaryKey: Boolean = false)

@Table(name = "users")
data class User(
@Column(name = "id", primaryKey = true)
val id: Long,

@Column(name = "username")
val name: String,

@Column("email")
val email: String
)

fun main() {
// 获取类上的注解
val tableAnnotation = User::class.findAnnotation<Table>()
println("表名: ${tableAnnotation?.name}") // 表名: users

// 获取所有属性上的注解
User::class.memberProperties.forEach { prop ->
val columnAnnotation = prop.findAnnotation<Column>()
println("${prop.name} -> ${columnAnnotation?.name}, PK: ${columnAnnotation?.primaryKey}")
}
// id -> id, PK: true
// name -> username, PK: false
// email -> email, PK: false
}

读取函数注解

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Route(val path: String, val method: String = "GET")

class ApiController {
@Route("/users", "GET")
fun getUsers(): List<String> = listOf("user1", "user2")

@Route("/users", "POST")
fun createUser(): String = "created"

@Route("/users/{id}", "DELETE")
fun deleteUser(): String = "deleted"
}

fun main() {
// 获取所有带 @Route 注解的方法
ApiController::class.memberFunctions
.filter { it.findAnnotation<Route>() != null }
.forEach { func ->
val route = func.findAnnotation<Route>()!!
println("${route.method} ${route.path} -> ${func.name}")
}
// GET /users -> getUsers
// POST /users -> createUser
// DELETE /users/{id} -> deleteUser
}

读取参数注解

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Body

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Query(val name: String)

class UserService {
fun search(
@Query("q") query: String,
@Query("page") page: Int,
@Body body: String
) { }
}

fun main() {
val func = UserService::class.memberFunctions.find { it.name == "search" }!!

func.valueParameters.forEach { param ->
val queryAnn = param.findAnnotation<Query>()
val bodyAnn = param.findAnnotation<Body>()

when {
queryAnn != null -> println("Query参数: ${param.name} -> ${queryAnn.name}")
bodyAnn != null -> println("Body参数: ${param.name}")
}
}
// Query参数: query -> q
// Query参数: page -> page
// Body参数: body
}

注解处理器

注解处理器(Annotation Processor)在编译时处理注解,可以生成新代码。虽然详细的注解处理器开发超出本章范围,这里介绍基本概念。

KSP(Kotlin Symbol Processing)

KSP 是 Kotlin 官方推荐的注解处理工具:

// build.gradle.kts
// plugins {
// id("com.google.devtools.ksp") version "1.9.0-1.0.13"
// }
// dependencies {
// ksp("com.example:processor:1.0.0")
// }

使用示例:自动生成 Builder

// 定义注解
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Builder

// 使用注解
@Builder
data class Person(
val name: String,
val age: Int,
val email: String? = null
)

// 注解处理器会自动生成:
// class PersonBuilder {
// var name: String? = null
// var age: Int? = null
// var email: String? = null
//
// fun build(): Person {
// return Person(
// name = name ?: throw IllegalArgumentException(),
// age = age ?: throw IllegalArgumentException(),
// email = email
// )
// }
// }

实战:自定义 ORM 映射

下面是一个使用注解实现简单 ORM 映射的示例:

import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.*

// 注解定义
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Entity(val tableName: String = "")

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Id(val autoGenerate: Boolean = true)

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Column(
val name: String = "",
val nullable: Boolean = true
)

// 实体类
@Entity("t_user")
data class User(
@Id
@Column("user_id")
val id: Long,

@Column("user_name", nullable = false)
val name: String,

@Column("user_email")
val email: String?,

@Column(nullable = false)
val createdAt: String
)

// ORM 映射工具
object OrmMapper {
// 获取表名
fun <T : Any> getTableName(clazz: KClass<T>): String {
val entityAnn = clazz.findAnnotation<Entity>()
return entityAnn?.tableName?.takeIf { it.isNotEmpty() }
?: clazz.simpleName?.lowercase() ?: ""
}

// 获取主键列
fun <T : Any> getPrimaryKey(clazz: KClass<T>): Pair<String, KProperty1<T, *>>? {
for (prop in clazz.memberProperties) {
if (prop.findAnnotation<Id>() != null) {
val columnAnn = prop.findAnnotation<Column>()
val columnName = columnAnn?.name?.takeIf { it.isNotEmpty() }
?: prop.name
return columnName to prop
}
}
return null
}

// 获取所有列映射
fun <T : Any> getColumnMappings(clazz: KClass<T>): Map<String, KProperty1<T, *>> {
return clazz.memberProperties.associate { prop ->
val columnAnn = prop.findAnnotation<Column>()
val columnName = columnAnn?.name?.takeIf { it.isNotEmpty() }
?: prop.name
columnName to prop
}
}

// 生成 SELECT SQL
fun <T : Any> generateSelectSql(clazz: KClass<T>): String {
val tableName = getTableName(clazz)
val columns = getColumnMappings(clazz).keys.joinToString(", ")
return "SELECT $columns FROM $tableName"
}

// 生成 INSERT SQL
fun <T : Any> generateInsertSql(clazz: KClass<T>, entity: T): String {
val tableName = getTableName(clazz)
val mappings = getColumnMappings(clazz)
val columns = mappings.keys.joinToString(", ")
val values = mappings.values.map { prop ->
val value = prop.get(entity)
when (value) {
is String -> "'$value'"
null -> "NULL"
else -> value.toString()
}
}.joinToString(", ")
return "INSERT INTO $tableName ($columns) VALUES ($values)"
}

// 从 ResultSet 映射到实体
fun <T : Any> mapFromResultSet(
clazz: KClass<T>,
data: Map<String, Any?>
): T? {
val constructor = clazz.primaryConstructor ?: return null

val args = constructor.parameters.associateWith { param ->
val prop = clazz.memberProperties.find { it.name == param.name }
val columnAnn = prop?.findAnnotation<Column>()
val columnName = columnAnn?.name?.takeIf { it.isNotEmpty() }
?: param.name ?: ""
data[columnName]
}

return try {
constructor.callBy(args)
} catch (e: Exception) {
null
}
}
}

fun main() {
// 演示 ORM 映射
println("=== ORM 映射示例 ===")
println("表名: ${OrmMapper.getTableName(User::class)}")

println("\n列映射:")
OrmMapper.getColumnMappings(User::class).forEach { (column, prop) ->
val isId = prop.findAnnotation<Id>() != null
println(" $column -> ${prop.name} ${if (isId) "[PK]" else ""}")
}

println("\nSELECT SQL:")
println(" ${OrmMapper.generateSelectSql(User::class)}")

val user = User(1L, "张三", "[email protected]", "2024-01-01")
println("\nINSERT SQL:")
println(" ${OrmMapper.generateInsertSql(User::class, user)}")

// 模拟从数据库读取
val dbData = mapOf(
"user_id" to 2L,
"user_name" to "李四",
"user_email" to "[email protected]",
"createdAt" to "2024-01-02"
)
println("\n映射到实体:")
val mappedUser = OrmMapper.mapFromResultSet(User::class, dbData)
println(" $mappedUser")
}

最佳实践

1. 选择正确的保留策略

// 编译时检查:使用 SOURCE
@Retention(AnnotationRetention.SOURCE)
annotation class CheckPermission

// 框架使用:使用 RUNTIME
@Retention(AnnotationRetention.RUNTIME)
annotation class Controller

2. 提供清晰的文档

/**
* 标记一个类为 REST 控制器。
*
* @param value 控制器的基础路径
* @since 1.0
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class RestController(val value: String = "")

3. 使用合理的默认值

// 提供合理的默认值
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Cache(
val key: String = "",
val ttl: Long = 3600, // 默认 1 小时
val enabled: Boolean = true
)

4. 组合注解

// 定义组合注解
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@JvmRecord
@Serializable
annotation class Dto

// 使用
@Dto
data class UserDto(val id: Long, val name: String)

// 需要导入的注解
annotation class Serializable
annotation class JvmRecord

小结

本章我们学习了 Kotlin 注解的核心内容:

  1. 注解基础:声明、参数、使用方式
  2. 元注解@Target@Retention@Repeatable@MustBeDocumented
  3. 使用目标@file:@field:@get:
  4. 内置注解@Deprecated@Suppress@JvmStatic@JvmField
  5. 反射读取:运行时获取注解信息
  6. 最佳实践:选择保留策略、提供文档、使用默认值

注解是 Kotlin 元编程的重要工具,结合反射可以实现强大的框架功能。理解注解的使用方式对于使用各种 Kotlin 框架(如 Spring、Ktor 等)至关重要。

练习

  1. 创建一个 @Log 注解,标记需要记录日志的方法
  2. 使用反射读取自定义注解,实现一个简单的依赖注入容器
  3. 创建 @Validate 注解,结合反射实现参数验证
  4. 实现一个使用注解的路由映射器
  5. 使用 KSP 或 KAPT 创建一个简单的注解处理器

参考资料