简单使用:
@Api(“https://api.github.com”)
interface GitHubApi {
@Get(“/users/{name}”)
fun getUser(@Path name: String): User
}
class User
第一个标注注解的注解主要是指前面的@Retention
和@Target
之类的,是写在注解类上的注解。
@file:JvmName(“KotlinAnnotations”)
@file:JvmMultifileClass
package com.bennyhuo.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(){
}
像 @Synchronized
@Throws
注解都是比较好用的,替代java的相应关键字,比较人性化了。其中 @file:JvmName("KotlinAnnotations")
和 @file:JvmMultifileClass
比较有意思,能让多个文件中的kotlin代码最终生成到一个类里面,假如还有一个文件如下:
@file:JvmName(“KotlinAnnotations”)
@file:JvmMultifileClass
package com.bennyhuo.kotlin.annotations.builtins
fun hello(){
}
那经过编译之后,这个文件会和上面的文件合并到一起,生成到一个kotlin类文件当中。
实例:仿 Retrofit 反射读取注解请求网络
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
}
@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 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= i n t e r f a c e s " ) / / 输出 [ G i t H u b A p i interfaces")// 输出 [GitHubApi interfaces")//输出[GitHubApiUsers, 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/
//动态代理
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()!!.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_url
和 avatarUrl
的相互映射。
//不写默认是RUNTIME
//@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class FieldName(val name: String)
@Target(AnnotationTarget.CLASS)
annotation class MappingStrategy(val klass: KClass)
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© // 否则直接拼接
}
//返回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©
}
//返回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 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().firstOrNull()?.name?.let(this::get))
// 拿到UserVO类的注解MappingStrategy的kclass即CamelToUnderScore,它是一个object calss, objectInstance获取实例,然后调用mapTo把avatarUrl转成avatar_url,最后调用userDTO[“avatar_url”]
?: To::class.findAnnotation()?.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()
parameter.findAnnotation()
下面两种写法是等价的:
let(this::get)
let{
this[it]
}
mapAs()方法中做了几件事:
-
尝试直接从当前Map中获取To对象的同名参数值,
-
尝试从To对象的字段上面的注解来获取需要转换的参数名,再根据名字获取Map中的值
-
尝试获取To对象的类注解得到处理类,调用处理类方法驼峰转下划线,再根据名字获取Map中的值
-
以上大招都没有获取到,如果To对象的字段可接受空值,就赋值null, 否则就抛异常
驼峰和下划线转换那里稍微有点绕。。
实例:注解处理器版 Model 映射
这个例子会用到一些著名的代码生成库:
-
生成Java代码:JavaPoet
-
生成Kotlin代码:KotlinPoet
上面两个都是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”)
}
注解声明:
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
[外链图片转存中…(img-mShJImE4-1714505717511)]
[外链图片转存中…(img-BSXTMcua-1714505717511)]
[外链图片转存中…(img-MZ4c6fDY-1714505717512)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!