手记

带着你一步一步在Kotlin使用注解,让你不再害怕注解

背景知识:

Kotlin中有以下四种元注解(用来定义注解的注解):

  1. @Target:限定注解标记的目标(属性、方法、类、扩展等等)

  2. @Retention:限定注解是否存储到字节码文件中;在运行时通过反射是否可见(默认情况下以上两个条件均为真)

  3. @Repeatable:允许在同一个元素上重复使用同一个注解

  4. @MustBeDocumented:指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。

在Kotlin中定义一个注解类,需要使用 annotation 关键字:

@Target(AnnotationTarget.PROPERTY)annotation class Valid

实际操作

说明:此次实例是对Spring框架中的Value注解进行简单的实现。大致的运行过程可以概括成这样:在某个类中为它的属性添加@Value(value="key")注解,在配置文件中为注解中出现的关键字赋予相应的值。最终通过注解解析器将配置文件中的值注入到添加了注解的属性中。

第一步:定义注解

说明:我们所定义的是一个属性层级的注解,并且需要在运行时获取注解的相关信息,注解含有一个String类型的参数。最终注解定义的代码是这样子的:

@Target(AnnotationTarget.PROPERTY)@Retention(AnnotationRetention.RUNTIME) //这一行也可以省略annotation class Value(val value:String)

第二步:对注解进行解析

说明:注解的定义十分的简单,就那么几行代码。但你要想让注解真正的起作用,你还需要对注解进行相应的解析才行。在解析注解的过程中会使用到大量和反射有关的代码,对反射的概念不熟悉的同学,先去看看反射要不然看接下来的这些代码会蛮吃力的。

class AnnotationExpression (val obj:Any){
fun expression(){        val clazz=obj::class
        clazz.declaredMemberProperties.forEach { prop->            val mutableProp= try{
                prop as KMutableProperty<*>
            }catch (e:Exception){                null
            } ?: return@forEach

            mutableProp.annotations.forEach { annotation->                val propClassName=mutableProp.returnType.toString().removePrefix("kotlin.")                when(propClassName) {                    in numtypeSet->mutableProp.setter.call(obj,
                            (readProp(annotation as Value) as kotlin.String).toNum(propClassName))                    "String"->mutableProp.setter.call(obj,
                            (readProp(annotation as Value) as kotlin.String))                    "Boolean"->mutableProp.setter.call(obj,
                            (readProp(annotation as Value) as kotlin.String).toBoolean())
                }
            }
        }
    }

通过KClass获取的KProperty1默认是不能被修改的,意味着你只能获取属性的值,而不能对其进行修改。所以在这里,我们对它进行了以下转换

prop as KMutableProperty<*>

因为可能出现使用该注解注释val变量的情况,在这里还进行了异常捕获,当发生异常时,直接跳过接下来的处理过程。

我们可以看到上面的代码中多次出现了 readProp 函数,在这里这个函数的作用是根据注解的信息,从配置文件中读取相应的数据。

private fun readProp(value:Value): Any? {    val prop=Properties()
    prop.load( AnnotationExpression::class.java.getResource("app.properties").openStream())    return prop.get(value.value)
}
# app.propertiesname="feint"age=11money=13.5gender=true

由于直接从property中获取的类型可能会和使用了@Value注解的属性的类型不匹配,因此我们需要根据属性的类型对从配置中获取的类型进行转换。

在这里适配了,布尔型、字符串型以及数字型的数据。由于数字型的类别特别多(Int,Double,Byte等等),便专门为String扩展了一个 toNum 函数,它接受一个String类型的参数,表示类型的名称。具体的代码是下面这样子的:

fun String.toNum(className:String):Any{    val clazz=Class.forName("java.lang.${typeMap[className]}")    return clazz.getMethod("parse$className",String::class.java).invoke(null,this)
}

这个地方又有一个坑,我本来是想通过反射调用Kotlin的String类中类似toInt、toDouble的方法。可是,运行后竟然提示,Kotlin的内置类型目前对反射的支持还不完善。。。无奈只好使用Java中那些包装类的parse方法。

第三步:使用注解

说明:使用的过程也没啥好说的,直接上代码

class User{    @Value(value = "name")
    lateinit var name:String    @Value(value = "age")
    var age:Int=0

    @Value(value = "money")
    var money:Double=0.0

    @Value(value = "gender")
    var gender:Boolean=false

    override fun toString(): String {        return "(name:$name; age:$age; money:$money; gender:${if(gender) "man" else "woman"})"
    }
}
fun main(args: Array<String>) {    val user=User()

    AnnotationExpression(user).expression()

    println(user.toString())
}

原文链接:http://www.apkbus.com/blog-822717-72705.html

0人推荐
随时随地看视频
慕课网APP