帶著你一步一步在Kotlin使用註解,讓你不再害怕註解
背景知識:
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.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):註解練習