1. 程式人生 > >帶著你一步一步在Kotlin使用註解,讓你不再害怕註解

帶著你一步一步在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.properties
name="feint"
age=11
money=13.5
gender=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())
}

原始碼地址(Github):註解練習