写点什么

Kotlin 学习手记——注解,flutter 下拉加载

用户头像
Android架构
关注
发布于: 刚刚

data class User(


var login: String,


var location: String,


var bio: String)


@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.CLASS)


annotation class Api(val url: String)


@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.CLASS)


annotation class Path(val url: String = "")


@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.FUNCTION)


annotation class Get(val url: String = "")


@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.VALUE_PARAMETER)


annotation class PathVariable(val name: String = "")


@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.VALUE_PARAMETER)


annotation class Query(val name: String = "")


@Api("https://api.github.com")


interface GitHubApi {


@Api("users")


interface Users {


@Get("{name}")


fun get(name: String): User


@Get("{name}/followers")


fun followers(name: String): List<User>


}


@Api("repos")


interface Repos {


@Get("{owner}/{repo}/forks")


fun forks(owner: String, repo: String)


}


}


object RetroApi {


const val PATH_PATTERN = """({(\w+)})"""


val okHttp = OkHttpClient()


val gson = Gson()


val enclosing = {


cls: Class<*> ->


var currentCls: Class<*>? = cls


sequence {


while(currentCls != null){


// enclosingClass 获取下一个 class


// yield 将对象添加到正在构建的 sequence 序列当中


currentCls = currentCls?.also { yield(it) }?.enclosingClass


}


}


}


//内联特化


inline fun <reified T> create(): T {


val functionMap = T::class.functions.map{ it.name to it }.toMap() //【函数名,函数本身】的 Pair 转成 map


val interfaces = enclosing(T::class.java).takeWhile { it.isInterface }.toList() //拿到所有接口列表


println("interfaces= Users, GitHubApi]


//foldRight 从 interfaces 序列的右边开始拼


val apiPath = interfaces.foldRight(StringBuilder()) {


clazz, acc ->


// 拿到每个接口类的 Api 注解的 url 参数值,如果 url 参数为空,则使用类名作为 url 值


acc.append(clazz.getAnnotation(Api::class.java)?.url?.takeIf { it.isNotEmpty() } ?: clazz.name)


.append("/")


}.toString()


println("apiPath= $apiPath") // https://api.github.com/users/


//动态代理


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


return Proxy.newProxyInstance(RetroApi.javaClass.classLoader, arrayOf(T::class.java)) {


proxy, method, args ->


//所有函数中的抽象函数 即接口的方法


functionMap[method.name]?.takeIf { it.isAbstract }?.let {


function ->


//方法的参数


val parameterMap = function.valueParameters.map {


//参数名和参数的值放在一起


it.name to args[it.index - 1] //valueParameters 包含 receiver 因此需要 index-1 来对应 args


}.toMap()


println("parameterMap= $parameterMap") //{name=bennyhuo}


//{name} 拿到 Get 注解的参数 如果注解参数不为空就使用注解参数,如果为空使用方法名称


val endPoint = function.findAnnotation<Get>()!!.url.takeIf { it.isNotEmpty() } ?: function.name


println("endPoint= $endPoint") //{name}/followers


//正则找到 endPoint 中的所有符合"{owner}/{repo}/forks"其中{xxx}的结果


val compiledEndPoint = Regex(PATH_PATTERN).findAll(endPoint).map {


matchResult ->


println("matchResult.groups= ${matchResult.groups}") // [MatchGroup(value={name}, range=0..5), MatchGroup(value={name}, range=0..5), MatchGroup(value=name, range=1..4)]


println("matchResult.groups1.range= ${matchResult.groups[1]?.range}") // 0..5


println("matchResult.groups2.value= ${matchResult.groups[2]?.value}") // name


matchResult.groups[1]!!.range to parameterMap[matchResult.groups[2]!!.value]


}.fold(endPoint) {


acc, pair ->


//acc 的初始值就是 endPoint 即{name}/followers


println("acc= ${acc}") // {name}/followers


println("pair= ${pair}") // (0..5, bennyhuo) pair 是一个 range to name


acc.replaceRange(pair.first, pair.second.toString()) // 把{name}/followers 中的 0 到 5 的位置的字符串{name}替换成 bennyhuo


}


println("compiledEndPoint= ${compiledEndPoint}") //bennyhuo/followers


//拼接 api 和参数


val url = apiPath + compiledEndPoint


println("url ==== $url")


println("*****************")


okHttp.newCall(Request.Builder().url(url).get().build()).execute().body()?.charStream()?.use {


gson.fromJson(JsonReader(it), method.genericReturnType)//返回 json 的解析结果


}


}


} as T


}


}


fun main() {


//interface com.bennyhuo.kotlin.annotations.eg.GitHubApi


//println("enclosingClass=${GitHubApi.Users::class.java.enclosingClass}")


val usersApi = RetroApi.create<GitHubApi.Users>()


val user = usersApi.get("bennyhuo")


val followers = usersApi.followers("bennyhuo").map { it.login }


println("user ====== $user")


println("followers ======== $followers")


}


这个例子还是有点复杂,不太好理解,有些方法没接触过不知道啥意思,这里加了很多打印方法,把结果打印输出一下,这样能知道具体是代表的啥,就好理解一点了。


实例:注解加持反射版 Model 映射


这个例子是在前面反射一节实现的 model 映射例子的基础上,通过添加注解方式处理那些字段名称不是相同风格的情况,比如两个对象中的avatar_urlavatarUrl的相互映射。


//不写默认是 RUNTIME


//@Retention(AnnotationRetention.RUNTIME)


@Target(AnnotationTarget.VALUE_PARAMETER)


annotation class FieldName(val name: String)


@Target(AnnotationTarget.CLASS)


annotation class MappingStrategy(val klass: KClass<out NameStrategy>)


interface NameStrategy {


fun mapTo(name: String): String


}


//下划线转驼峰


object UnderScoreToCamel : NameStrategy {


// html_url -> htmlUrl


override fun mapTo(name: String): String {


//先转成字符数组,然后 fold 操作


return name.toCharArray().fold(StringBuilder()) { acc, c ->


when (acc.lastOrNull()) { //上一次的 acc 不是空


'_' -> acc[acc.lastIndex] = c.toUpperCase() //上一次结果的最后一个字符是下划线就把下划线位置替换成当前字符的大写字母


else -> acc.append(c) // 否则直接拼接


}


//返回 acc


acc


}.toString()


}


}


//驼峰转下划线


object CamelToUnderScore : NameStrategy {


override fun mapTo(name: String): String {


//先转成字符数组,然后 fold 操作


return name.toCharArray().fold(StringBuilder()) { acc, c ->


when {


c.isUpperCase() -> acc.append('_').append(c.toLowerCase()) //如果是大写字母直接拼一个下划线再拼上小写


else -> acc.append(c)


}


//返回 acc


acc


}.toString()


}


}


//使用定义的策略注解,驼峰转下划线


@MappingStrategy(CamelToUnderScore::class)


data class UserVO(


val login: String,


//@FieldName("avatar_url") //这种是单个字段上面添加注解,只能一个一个添加


val avatarUrl: String,


var htmlUrl: String


)


data class UserDTO(


var id: Int,


var login: String,


var avatar_url: String,


var url: String,


var html_url: String


)


fun main() {


val userDTO = UserDTO(


0,


"Bennyhuo",


"https://avatars2.githubusercontent.com/u/30511713?v=4",


"https://api.github.com/users/bennyhuo",


"https://github.com/bennyhuo"


)


val userVO: UserVO = userDTO.mapAs()


println(userVO)


val userMap = mapOf(


"id" to 0,


"login" to "Bennyhuo",


"avatar_url" to "https://api.github.com/users/bennyhuo",


"html_url" to "https://github.com/bennyhuo",


"url" to "https://api.github.com/users/bennyhuo"


)


val userVOFromMap: UserVO = userMap.mapAs()


println(userVOFromMap)


}


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]


// let(this::get)等价于 let{this[it]} userDTO["avatar_url"]


?: (parameter.annotations.filterIsInstance<FieldName>().firstOrNull()?.name?.let(this::get))


// 拿到 UserVO 类的注解 MappingStrategy 的 kclass 即 CamelToUnderScore,它是一个 object calss, objectInstance 获取实例,然后调用 mapTo 把 avatarUrl 转成 avatar_url,最后调用 userDTO["avatar_url"]


?: 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)


}


}


这里如果注解上不写@Retention(AnnotationRetention.RUNTIME)默认就是运行时类型。


下面两种写法是等价的:


parameter.annotations.filterIsInstance<FieldName>()


parameter.findAnnotation<FieldName>()


下面两种写法是等价的:


let(this::get)


let{


this[it]


}


mapAs()方法中做了几件事:


  1. 尝试直接从当前 Map 中获取 To 对象的同名参数值,

  2. 尝试从 To 对象的字段上面的注解来获取需要转换的参数名,再根据名字获取 Map 中的值

  3. 尝试获取 To 对象的类注解得到处理类,调用处理类方法驼峰转下划线,再根据名字获取 Map 中的值

  4. 以上大招都没有获取到,如果 To 对象的字段可接受空值,就赋值 null, 否则就抛异常


驼峰和下划线转换那里稍微有点绕。。


实例:注解处理器版 Model 映射





这个例子会用到一些著名的代码生成库:



上面两个都是 square 公司出品的开源库,JakeWharton大神的杰作,这个例子中主要用到了 KotlinPoet,还有一个这个学习课程资料主讲大神自己写的一个库。


dependencies {


implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"


implementation "com.squareup:kotlinpoet:1.4.3"


implementation "com.bennyhuo.aptutils:aptutils:1.7.1"


implementation project(":apt:annotations")


}


注解声明:


@Retention(AnnotationRetention.BINARY)


@Target(AnnotationTarget.CLASS)


annotation class ModelMap


这里不需要在运行时保留注解,编译就会生成代码了,因此使用的是AnnotationRetention.BINARY


注解生成代码:


package com.bennyhuo.kotlin.annotations.apt.compiler


import com.bennyhuo.aptutils.AptContext


import com.bennyhuo.aptutils.logger.Logger


import com.bennyhuo.aptutils.types.ClassType


import com.bennyhuo.aptutils.types.asKotlinTypeName


import com.bennyhuo.aptutils.types.packageName


import com.bennyhuo.aptutils.types.simpleName


import com.bennyhuo.aptutils.utils.writeToFile


import com.bennyhuo.kotlin.annotations.apt.ModelMap


import com.squareup.kotlinpoet.*


import javax.annotation.processing.*


import javax.lang.model.SourceVersion


import javax.lang.model.element.ExecutableElement


import javax.lang.model.element.TypeElement


import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy


//必须指定注解的类型


@SupportedAnnotationTypes("com.bennyhuo.kotlin.annotations.apt.ModelMap")


@SupportedSourceVersion(SourceVersion.RELEASE_8)


class ModelMapProcessor: AbstractProcessor() {


override fun init(processingEnv: ProcessingEnvironment) {


super.init(processingEnv)


AptContext.init(processingEnv)


}


//fun Sample.toMap() = mapOf("a" to a, "b" to b)


//fun <V> Map<String, V>.toSample() = Sample(this["a"] as Int, this["b"] as String)


override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {


roundEnv.getElementsAnnotatedWith(ModelMap::class.java)


.forEach {


element ->


element.enclosedElements.filterIsInstance<ExecutableElement>()


.firstOrNull { it.simpleName() == "<init>" }


?.let {


val typeElement = element as TypeElement


FileSpec.builder(typeElement.packageName(), "${typeElement.simpleName()}



转义


.addFunction(


FunSpec.builder("toMap")


.receiver(typeElement.asType().asKotlinTypeName())


.addStatement("return mapOf({it.parameters.joinToString {""""{it.simpleName()}" to ${it.simpleName()}""" }})")//mapOf("a" to a, "b" to b)


.build()


)


.addFunction(


FunSpec.builder("to${typeElement.simpleName()}")


.addTypeVariable(TypeVariableName("V"))


.receiver(MAP.parameterizedBy(STRING, TypeVariableName("V")))


.addStatement(

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Kotlin学习手记——注解,flutter下拉加载