【Kotlin】 自学(九)-Kotlin注解

585 阅读3分钟

注解基本概念

注解概念

  • 注解是对程序附加信息说明
  • 注解可以对类,函数,函数参数,属性等做标注
  • 注解的信息可用于源码级,编译期,运行时

注解定义

使用annotation标记类

annotation class KotlinAnnotationClass

使用Target来限定作用,比如只能作用函数

@Target(AnnotationTarget.FUNCTION)
annotation class KotlinAnnotationClass

这个AnnotationTarget是一个枚举类,里面有好多定义好的属性

public enum class AnnotationTarget {
    CLASS, //表示作用对象有类、接口、object对象表达式、注解类
    ANNOTATION_CLASS,//表示作用对象只有注解类
    TYPE_PARAMETER,//表示作用对象是泛型类型参数(暂时还不支持)
    PROPERTY,//表示作用对象是属性
    FIELD,//表示作用对象是字段,包括属性的幕后字段
    LOCAL_VARIABLE,//表示作用对象是局部变量
    VALUE_PARAMETER,//表示作用对象是函数或构造函数的参数
    CONSTRUCTOR,//表示作用对象是构造函数,主构造函数或次构造函数
    FUNCTION,//表示作用对象是函数,不包括构造函数
    PROPERTY_GETTER,//表示作用对象是属性的getter函数
    PROPERTY_SETTER,//表示作用对象是属性的setter函数
    TYPE,//表示作用对象是一个类型,比如类、接口、枚举
    EXPRESSION,//表示作用对象是一个表达式
    FILE,//表示作用对象是一个File
    @SinceKotlin("1.1")
    TYPEALIAS//表示作用对象是一个类型别名
}

使用Retention来指定作用时机,源码期,编译期,运行时

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class KotlinAnnotationClass

这个AnnotationRetention也是一个枚举类

public enum class AnnotationRetention {
    SOURCE,//源代码时期(SOURCE): 注解不会存储在输出class字节码中
    BINARY,//编译时期(BINARY): 注解会存储出class字节码中,但是对反射不可见
    RUNTIME//运行时期(RUNTIME): 注解会存储出class字节码中,也会对反射可见, 默认是RUNTIME
}

作用时机大小如下:

SOURCE<BINARY<RUNTIME

添加参数

类似构造函数,但是支持类型有限,支持以下类型及其数组

  • 基本类型
  • KClass
  • 枚举
  • 其他注解
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class KotlinAnnotationClass(val url: String)

注解的使用

class Test {

    @KotlinAnnotationClass("http://www.baidu.com")
    fun test() {

    }
}

常见内置注解使用

内置注解

  • kotlin.annotation.* :用于标注注解的注解
  • kotlin.*:标准库的一些通用用途注解
  • kotlin.jvm.*:用于与Java虚拟机的交互

标准注解的注解

Target,Retention:限定注解的使用范围和时机 AnnotationTarget,AnnotationRetention:作为TargetRetention的参数

标准库的通用注解

Metadata:Kotlin反射的信息通过该注解附带在元素上 UnsafeVariance:泛型用来破除型变限制 Suppress:用来去除编译器警告

Java虚拟机相关注解

  • JvmField:生成JavaField
  • JvmName:指定类,函数等生成的JVM名字
  • JvmOverloads:函数默认参数生成函数重载
  • JvmStatic:生成静态成员
  • Synchronized:标记为同步类型
  • Throws:标记函数抛出异常类型
  • Volatile:生成volatileField
@file:JvmName("KotlinAnnotations")
package com.greathfs.kotlin.annotations.builtins

import java.io.IOException

@Volatile
var volatileProperty: Int = 0

@Synchronized
fun synchronizedFunction(){

}

val lock = Any()
fun synchronizedBlock(){
    synchronized(lock) {

    }
}

@Throws(IOException::class)
fun throwException(){

}

注解使用案例

注解加持反射版Model映射

我们在上一篇反射中写过一个Model映射案例,要求名字都得对的上,但是实际我们开发过程中有可能后台返回字段或者数据库中存的是下划线,但是实际需要驼峰式,比如:

data class UserVO(
        var id: Int,
        val login: String,
        val avatarUrl: String,
        var url: String,
        var htmlUrl: String
)

data class UserDTO(
        var id: Int,
        var login: String,
        var avatar_url: String,
        var url: String,
        var html_url: String
)

这样如果按照之前那种写法就不能实现映射了,所以我们改良下,有两种方法:

  • 写一个属性注解FieldName指定字段名字,可能需要写多次
  • 写一个策略注解MappingStrategy,所有的字段都按照这个策略执行

我们先把反射映射那段代码拿过来

inline fun <reified From : Any, reified To : Any> From.mapAs(): To {
    return From::class.memberProperties.map { it.name to it.get(this) }
        .toMap().mapAs()
}

inline fun <reified To : Any> Map<String, Any?>.mapAs(): To {
    return To::class.primaryConstructor!!.let {
        it.parameters.map {
            parameter ->
            //找名字
            parameter to (this[parameter.name] ?: if(parameter.type.isMarkedNullable) null
            else throw IllegalArgumentException("${parameter.name} is required but missing."))
        }.toMap()
            .let(it::callBy)
    }
}

  • 第一种方式:

首先我们要新建一个注解FieldName

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class FieldName(val name: String)

然后我们修改下mapAs()代码逻辑,原来逻辑是如果在在map里面找不到直接抛出异常,我们在抛出异常前再判断下有没有使用FieldName,如果使用了,我们就取这个注解里面的名字

inline fun <reified To : Any> Map<String, Any?>.mapAs(): To {
    return To::class.primaryConstructor!!.let {
        it.parameters.map { parameter ->
            parameter to (this[parameter.name]
            		//判断有没有注解
                    ?: (parameter.annotations.filterIsInstance<FieldName>().firstOrNull()?.name?.let(this::get))
                    ?: if (parameter.type.isMarkedNullable) null
                    else throw IllegalArgumentException("${parameter.name} is required but missing."))
        }.toMap()
                .let(it::callBy)
    }
}

这里找注解还可以使用findAnnotation

然后我们只需要在需要的属性上加上注解就行

data class UserVO(
    val login: String,
    @FieldName("avatar_url")
    val avatarUrl: String,
    var htmlUrl: String
)
  • 第二种方式:

我们这里要实现两种,下划线转换驼峰,驼峰转换下划线

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class MappingStrategy(val klass: KClass<out NameStrategy>)

interface NameStrategy {
    fun mapTo(name: String): String
}

/**
 *  // html_url -> htmlUrl
 *  下划线转驼峰
 */
object UnderScoreToCamel : NameStrategy {
   
    override fun mapTo(name: String): String {
        return name.toCharArray().fold(StringBuilder()) { acc, c ->
            when (acc.lastOrNull()) {
                '_' -> acc[acc.lastIndex] = c.toUpperCase()
                else -> acc.append(c)
            }
            acc
        }.toString()
    }
}

/**
 *  // htmlUrl -> html_url
 *  驼峰转下划线
 */
object CamelToUnderScore : NameStrategy {
    override fun mapTo(name: String): String {
        return name.toCharArray().fold(StringBuilder()) { acc, c ->
            when {
                c.isUpperCase() -> acc.append('_').append(c.toLowerCase())
                else -> acc.append(c)
            }
            acc
        }.toString()
    }
}

简单解释下:基本上都是先把属性拆成字符集合,然后遍历集合,然后根据定义好的策略进行转化

然后我们跟第一种一样,修改下mapAs()代码逻辑

inline fun <reified To : Any> Map<String, Any?>.mapAs(): To {
    return To::class.primaryConstructor!!.let {
        it.parameters.map { parameter ->
            parameter to (this[parameter.name]
                ?: (parameter.annotations.filterIsInstance<FieldName>().firstOrNull()?.name?.let(this::get))
                ?: To::class.findAnnotation<MappingStrategy>()?.klass?.objectInstance?.mapTo(parameter.name!!)?.let(this::get)
                ?: if (parameter.type.isMarkedNullable) null
                else throw IllegalArgumentException("${parameter.name} is required but missing."))
        }.toMap()
            .let(it::callBy)
    }
}

使用的话也很简单

@MappingStrategy(CamelToUnderScore::class)
data class UserVO(
    val login: String,
    //@FieldName("avatar_url")
    val avatarUrl: String,
    var htmlUrl: String
)