声明注解
Kotlin中声明主角用 annotation class 关键字,注解也是一种class,编译器同样可以对注解类型在编译器进行类型检查,实例如下:
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class Test {
}
- 自定义注解中的 @Target @Retention 等称之为元注解(Meta - annotation)。元注解说明如下
元注解名称 | 功能说明 |
---|---|
@Target | 指定这个注解可被用于哪些元素(在public enum class AnnotationTarget 中定义了枚举)类Class、注解类ANNOTATION_CLASS、泛型参数TYPE_PARAMETER,函数FUNCTION、属性PROPERTY、成员变量FIELD、局部变量LOCAL_VARIABLE、VALUE_PARAMETER、CONSTRUCTOR、PROPERTY_GETTER、PROPERTY_SETTER,接口描述类、接口、enum的TYPE,表达式EXPRESSION,文件FILE,类型别名TYPEALIAS |
@Retenion | 指定这个注解的信息是否被保存到编译后的class文件中,以及在运行时是否可以通过反射访问到它。可取值有三个:SOURCE(注解数据不存储在二进制输出中),BINARY(注解的数据存储到二进制输出中,但是反射不可见。),RUNTIME(注解数据存储在二进制输出中,可用于反射,默认值是这个) |
@Repeatable | 允许在单个元素上多次使用同一个注解 |
@MustBeDocumented | 表示这个注解是公开API的一部分,在自动生成API文档的类或者函数签名中,应该包含这个注解信息 |
使用注解
使用方式如下
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class TestRun {
}
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class TestCase(val id: String) {
}
@TestRun
class Test {
@TestCase("1")
fun testCase(testId: String) {
println(testId)
}
}
其中,注解参数可以支持的类型如下
- 基本数据类型
- String类型
- KClass类型
- enum类型
- Annotation
- 以及以上类型(除基本数据类型)的数组。
annotation class TestCase(val id: String){}
annotation class TestCase(val id: Int){}
annotation class TestCase(val id: Array<String>){}
annotation class TestCase(val id: TestRun){}
annotation class TestCase(val id: KClass<String>){}
以下编译不通过
annotation class TestCase(val id: Array<Int>){}
annotation class TestCase(val id: Test){}
注解中不能有null类型,因为JVM不支持将null作为注解属性值就行存储。如果注解用作另一个注解时,名称不能以@字符为前缀。
annotation class AnnX {}
annotation class AnnY(val message: String,
val annX: AnnX = AnnX()) {
}
java注解和Kotlin注解完全兼容。
处理了注解
- 定义了注解,并在需要的时候给相关位置添加上注解信息后,如何让注解发挥作用,核心就在于处理注解的代码了。接下来我们了解一下注解的获取和处理。
以上面的两个注解为例,调用如下
@TestRun
class AnnTest {
@TestCase("1")
fun test(textId: String) {
}
}
1. ::class引用
首先声明一个变量指向AnnTest
val annTest = AnnTest()
然后通过这个变量来获取该对象的类的信息。使用::class来获取KClass的引用
val kClass= annTest::class
2. declaredFunctions扩展属性
下面我们要获取annTest 对象类型所声明的所有函数。Kotlin中直接使用扩展属性declaredFunctions来获取这个类中声明的所有函数(对应的反射类型是KFunction)。
val declaredFunctions = kClass.declaredFunctions
返回的是一个Collection<KFunction<*>>其中< * >是Kotlin中的星投影,类似于java中的<?>通配符。
declaredFunctions源码如下:
@SinceKotlin("1.1")
val KClass<*>.declaredFunctions: Collection<KFunction<*>>
get() = (this as KClassImpl).data().declaredMembers.filterIsInstance<KFunction<*>>()
3. annotations属性
public interface KFunction<out R> : KCallable<R>, Function<R>
KFunction继承了KCallable,KCallable又继承了KAnnotatedElement。KAnnotatedElement中有个annotations: List< Annotation >方法
public interface KAnnotatedElement {
/**
* 存储了该函数所有注解信息。
*/
public val annotations: List<Annotation>
}
通过遍历这个List可以获取到@TestCase注解。
for (f in declaredFunctions) {
f.annotations.forEach {
if (it is TestCase) {
val id = it.id
println(id) //可以自己做一些操作了
}
}
}
- call函数
如果想通过反射来调用函数,可以直接使用call()函数
f.call(annTest,id)
等价于 f.javaMethod?.invoke(annTest,id)
完整代码如下:
import kotlin.reflect.full.declaredFunctions
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class TestRun {
}
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class TestCase(val id: String) {
}
@TestRun
class AnnTest {
@TestCase("1")
fun test(textId: String) {
}
}
fun parseAnn(){
val annTest = AnnTest()
val kClass = annTest::class
val declaredFunctions = kClass.declaredFunctions
for (f in declaredFunctions) {
f.annotations.forEach {
if (it is TestCase) {
val id = it.id
println(id)
f.call(annTest,id)
}
}
}
}
fun main(args: Array<String>) {
parseAnn()
}
测试代码如下
fun main(args: Array<String>) {
parseAnn()
}