背景知识:
Kotlin中有以下四种元注解(用来定义注解的注解):
@Target:限定注解标记的目标(属性、方法、类、扩展等等)
@Retention:限定注解是否存储到字节码文件中;在运行时通过反射是否可见(默认情况下以上两个条件均为真)
@Repeatable:允许在同一个元素上重复使用同一个注解
@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()) }
共同学习,写下你的评论
评论加载中...
作者其他优质文章