注解¶
注解用于给代码元素附加元数据。编译器、框架、代码生成器、测试工具、依赖注入容器、序列化库都可能读取这些元数据并改变行为。
Java 开发者对注解通常很熟悉,但 Kotlin 的属性、主构造函数、use-site target 和文件级注解会带来一些新的细节。
声明注解¶
annotation class Fancy
使用:
@Fancy
class User
注解本身是一种特殊的类,用 annotation class 声明。
元注解¶
可以用元注解限制注解的用途:
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER
)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Audit
常见元注解:
@Target:允许标注哪些元素。@Retention:注解保留到源码、字节码还是运行时。@Repeatable:允许重复使用同一个注解。@MustBeDocumented:表示它属于公共 API 文档的一部分。
注解参数¶
annotation class Route(val path: String, val method: String = "GET")
@Route("/users")
class UserController
注解参数允许的类型包括:
- 基本类型对应类型,例如
Int、Long。 String。- 类引用,例如
User::class。 - 枚举。
- 其他注解。
- 上述类型的数组。
注解参数不能是可空类型,因为 JVM 注解属性不能存储 null。
类引用参数¶
import kotlin.reflect.KClass
annotation class HandlerFor(val type: KClass<*>)
@HandlerFor(User::class)
class UserHandler
Kotlin 中用 KClass 表达类引用。编译到 JVM 后,Java 仍能按普通 annotation class 参数读取。
注解主构造函数¶
如果要注解主构造函数,需要显式写 constructor:
class Service @Inject constructor(
private val repository: Repository
)
如果只是注解构造参数:
class Service(
@Named("main") private val repository: Repository
)
注解属性访问器¶
class User {
var name: String = ""
@Inject set
}
这类写法不常见,但在依赖注入、序列化、Java Bean 框架互操作中可能遇到。
use-site target¶
Kotlin 一个属性可能在 JVM 上生成多个元素:
- 构造函数参数。
- backing field。
- getter。
- setter。
- Kotlin property 元数据。
因此有时必须明确注解应该放到哪里:
data class User(
@field:Email
val email: String
)
常见 target:
| target | 含义 |
|---|---|
@field: |
Java 字段 |
@get: |
getter |
@set: |
setter |
@param: |
构造函数参数 |
@property: |
Kotlin 属性,Java 不可见 |
@file: |
文件 |
@receiver: |
扩展函数或属性的接收者 |
@delegate: |
委托属性保存委托对象的字段 |
Bean Validation 示例¶
Java 生态中的校验注解经常要求打到字段或 getter 上。Kotlin 写法要根据框架读取位置选择:
data class RegisterRequest(
@field:Email
@field:NotBlank
val email: String
)
如果写成:
data class RegisterRequest(
@Email
val email: String
)
编译器会按规则选择默认目标,但这个默认目标不一定是框架读取的位置。对 Java 框架互操作时,显式写 @field: 或 @get: 更稳。
文件级注解¶
文件级注解放在 package 之前:
@file:JvmName("StringUtils")
package demo
fun normalize(value: String) = value.trim()
这会影响顶层函数在 Java 中生成的类名。
all meta-target¶
Kotlin 的 @all: 可以把注解传播到多个相关位置,例如构造参数、属性、字段、getter、setter 参数等:
data class User(
@all:Email
val email: String
)
这对 Java records、Bean Validation 等场景有帮助。但它也有限制,例如不能用于委托属性,不能用 @all:[A B] 一次包多个注解。
Java 注解在 Kotlin 中使用¶
Java:
public @interface Ann {
int intValue();
String stringValue();
}
Kotlin 必须用命名参数:
@Ann(intValue = 1, stringValue = "abc")
class Demo
如果 Java 注解参数名是 value,可以省略参数名:
@AnnWithValue("abc")
class Demo
数组参数:
@AnnWithArrayMethod(names = ["a", "b", "c"])
class Demo
重复注解¶
@Repeatable
annotation class Tag(val name: String)
@Tag("api")
@Tag("stable")
class UserApi
Kotlin 和 Java 都支持 repeatable annotations,但生成容器注解的机制细节不同。需要与 Java 框架深度互操作时,关注 @JvmRepeatable。
KSP 与注解处理¶
如果你要基于注解做代码生成,Kotlin 官方更推荐 Kotlin Symbol Processing,也就是 KSP。相比 Java 注解处理器,KSP 更理解 Kotlin 语法和符号模型,例如属性、扩展、空性、默认参数等。
Java 的 APT/KAPT 仍然可用,但在纯 Kotlin 项目中,优先评估 KSP。
实践建议¶
- 与 Java 框架互操作时,显式使用
@field:、@get:、@param:。 - 注解参数不能可空,默认值要认真设计。
- 文件级注解必须放在
package前。 - 自定义注解时明确
@Retention,避免运行时读取不到。 - 新的 Kotlin 代码生成优先考虑 KSP。
参考¶
- 官方注解文档:https://kotlinlang.org/docs/annotations.html