注解处理器在架构,框架中实战应用:MVVM中数据源提供Repository类的自动生成

前言:力尽不知热 , 但惜夏日长。带你走进APT不寻常的实战
大多数注解处理器上手时:

  1. 接触的是 butterknife,> 可以自动生成 模版式代码 findViewById ,
  2. 或者EventBus这种去找到注解的类,找到注解的方法。
  3. 本文详细介绍注解处理器的用法, 自动生成 Repository ,或者根据一个接口类,生成另一个类来实现该接口的所有方法,当然这些方法的实现和参数是有固定模版式写法的。

本文示例介绍思路

一、前言

  1. APT注解处理器是什么?
    APT:即Annotation Processing Tool,即注解处理器,是在程序编译期间的一种处理注解的工具,确切的说它是javac的一个工具。注解处理器以Java代码或者Kotlin作为输入,生成.java或者.Kotlin文件作为输出.
  2. Android中哪些框架中使用了APT?
    butterknife框架,EventBus框架,JetPack.Room库,Glide图片加载扩展OKhttp时,ARouter路由框架,GreenDAO数据库等框都使用过APT
  3. 本文带你走进APT哪种应用场景?
    生成接口类实现,接口类实现是固定模板。
  4. 在Kotlin中叫作Kapt , Java中叫作APT
    Kapt:即:全称是 Kotlin annotation processing tool)就可以将 Java 注解处理器与 Kotlin 代码搭配使用了

由于现在大部分开发都是使用Kotlin开发,本文主要讲述的是Kapt的角度讲解实例,Java中大同小异。

本示例将会介绍:

  1. 网络请求框架的架构中 数据源提供Repository怎么根据Retrofit的Interface 接口自动生成。
  2. 同理可以扩展联想到自己项目中哪些模版式代码,接口的模版式实现也可以这样自动生成。
  3. Kotlin中Kapt的使用场景及KotlinPoet的相关使用。
  4. 同时思考某些网络接口数据可以单独解耦出模块来。

二 APT中基础介绍:元注解

  • 1.@Target:目标,表示注解修饰的目标

    Java中写法
    ElementType.ANNOTIONS_TYPE : 目标是注解,给注解设置的注解
    ElementType.CONSTRUCTOR : 构造方法
    ElementType.FIELD : 属性注解
    ElementType.METHOD : 方法注解
    ElementType.Type : 类型如:类,接口,枚举
    ElementType.PACKAGE : 可以给一个包进行注解
    ElementType.PARAMETER : 可以给一个方法内的参数进行注解
    ElementType.LOCAL_VARIABLE: 可以给局部变量进行注解

    Kotlin的写法:
    AnnotationTarget.CLASS 可以给一个类型进行注解,类、接口、对象、甚至注解类本身
    AnnotationTarget.ANNOTATION_CLASS : 可以给一个注解类进行注解 AnnotationTarget.TYPE_PARAMETER : 泛型参数(暂未支持) AnnotationTarget.VALUE_PARAMETER : 方法、构造函数的参数 AnnotationTarget.PROPERTY : (计算)属性(该注解Java不可见) AnnotationTarget.PROPERTY_GETTER : 属性getter方法
    AnnotationTarget.PROPERTY_SETTER : 属性setter方法
    AnnotationTarget.FIELD : 字段变量,包括PROPERTY的备用字段(backing field)
    AnnotationTarget.LOCAL_VARIABLE : 局部变量
    AnnotationTarget.CONSTRUCTOR : 构造函数
    AnnotationTarget.FUNCTION : 方法、函数(不包括构造函数)
    AnnotationTarget.FILE : 文件整体
    AnnotationTarget.TYPE : 泛型支持
    AnnotationTarget.TYPEALIAS : typealias类型
    AnnotationTarget.EXPRESSION: 表达式

  • 2.@Retention:表示需要在什么级别保存该注解信息

    Java中
    RetentionPolicy.SOURCE :注解仅存在于源码中,编译器编译后的class中会消失,通常用于代码的检查,比如@overwrite,当父类或接口没有此方法时会编译失败。
    RetentionPolicy.CLASS :注解会存在编译后的class文件中,但是JVM不会加载,默认值,通常和RetentionPolicy.SOURCE无差别,只有在进行java字节码编程时会用到。 RetentionPolicy.RUNTIME :注解会存在编译后的class文件中,并被JVM读取,通过反射方式获取注解的值,在Junit等测试框架中被大量使用


    Kotlin中对应写法:
    AnnotationRetention.SOURCE
    AnnotationRetention.BINARY
    AnnotationRetention.RUNTIME

3.@Document:将注解包含到javaDoc中

4.@Inherit:运行子类继承父类的注解

5.@Repeatable:定义注解可重复

三、项目目录介绍

如上图所示,项目Demo 分为4个部分:

  1. networkApiData:Java Library,项目网络接口部分和 网络数据返回实体bean,可以将网络部分解耦出来
  2. annotations:Java Library, 注解模块
  3. annotation_compiler:Java Library 注解处理模块
  4. app:真实android app 工程

四、网络模块部分编写(networkApiData)

build.gradle里面添加 dependencies 里面 添加依赖

java 复制代码
  api project(path: ':annotations')

主要示例了Retrofit中请求接口写法

  1. 示例了Get请求
  2. 示例Post 请求 参数在url上
  3. 示例post 请求 post body
  4. 示例post 请求 post body 在Repository中的 第2种写法
less 复制代码
interface Api {

    //示例Get 请求
    @GET("search/acjson?tn=resultjson_com&logid=12307192414549550342&ipn=rj&ct=201326592&is=&fp=result&fr=&cg=star&rn=30")
    suspend fun get899(@Query("word") word: String, @Query("queryWord") queryWord: String, @Query("pn") pn: Int, @Query("gsm") gsm: String): BaseResponse<ArrayList<BaiduDataBean>>

    //示例Post 请求 参数在url上
    @FormUrlEncoded
    @POST("https://www.wanandroid.com/user/register")
    suspend fun register(
        @Field("username") username: String,
        @Field("password") password: String,
        @Field("repassword") repassword: String
    ): String


    //示例post 请求 post body
    // 此处示例写法,这个真实post body 地址是不通的
    @POST("https://www.wanandroid.com/user/register")
    suspend fun testPostBody(@Body body: RequestBody): String

    // 示例post 请求 post body
    // 此处示例写法,这个真实post body 地址是不通的
    @PostBody("{"ID":"Long","name":"String"}")
    @POST("https://www.wanandroid.com/user/register")
    suspend fun testPostBody222(@Body body: RequestBody): String
}

五、注解模块(annotations)

主要写了2个注解:

  1. CreateService 注解在网络接口类上面
  2. PostBody: 注解在post 请求body上面
less 复制代码
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CreateService(val interfaceApi: String, val superClass: String) {
    /**
     * interfaceApi:  接口类名字,
     *  superClass :  自动生成的类的继承的父类
     **/
}
less 复制代码
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PostBody(val json: String) {

}

六、真实android app 工程 里面

build.gradle里面添加

bash 复制代码
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'//添加 kapt
}

开启 kapt

ini 复制代码
 kapt {
        generateStubs = true
    }

dependencies 里面 添加依赖

java 复制代码
    implementation project(path: ':networkApiData')
    kapt project(path: ':annotation_compiler')

然后随便代码工程目录里面写个类,类名可自定义:

kotlin 复制代码
/**
 * interfaceApi:  接口类名字,
 *  superClass :  自动生成的类的继承的父类
 **/
@CreateService(interfaceApi = "com.wx.test.api.Api", superClass = "com.wx.kotlin_kapt_demo.data_source.repository.BaseRepository")
class KaptComponet {
}

七、注解处理模块(annotation_compiler)

主要负责自动成Repository 类

  1. build.gradle里面编写
php 复制代码
plugins {
    id 'java'
    id 'java-library'
    id 'kotlin'
    id 'kotlin-kapt'
}


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'  java 用法
//    implementation 'com.google.auto.service:auto-service:1.0-rc4'    java 用法

    implementation "com.google.auto.service:auto-service:1.0-rc4"  // kotlin 用法
    kapt "com.google.auto.service:auto-service:1.0"                // kotlin 用法
    implementation "com.squareup:kotlinpoet:1.8.0"
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'

    implementation project(path: ':annotations')
    implementation project(path: ':networkApiData')
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
  1. 自动生成代码处理类 AptProcessor:
kotlin 复制代码
@AutoService(Processor::class)
class AptProcessor : AbstractProcessor() {

    private var mFiler: Filer? = null

    private var mElementUtils: Elements? = null
    private val gson by lazy { Gson() }

    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        mFiler = processingEnv?.filer
        mElementUtils = processingEnv?.elementUtils
    }

    //指定处理的版本
    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latestSupported()
    }

    //给到需要处理的注解
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        val types: LinkedHashSet<String> = LinkedHashSet()
        getSupportedAnnotations().forEach { clazz: Class<out Annotation> ->
            types.add(clazz.canonicalName)
        }
        return types
    }

    private fun getSupportedAnnotations(): Set<Class<out Annotation>> {
        val annotations: LinkedHashSet<Class<out Annotation>> = LinkedHashSet()
        // 需要解析的自定义注解
        annotations.add(CreateService::class.java)
        annotations.add(PostBody::class.java)
        return annotations
    }

    /**
    KotlinPoet 官方helloWorld示例:
    val greeterClass = ClassName("", "Greeter")
    val file = FileSpec.builder("", "HelloWorld")
    .addType(TypeSpec.classBuilder("Greeter")
    .primaryConstructor(FunSpec.constructorBuilder()
    .addParameter("name", String::class).build())
    .addProperty(PropertySpec.builder("name", String::class)
    .initializer("name").build())
    .addFunction(FunSpec.builder("greet")
    .addStatement("println(%P)", "Hello, $name").build())
    .build())
    .addFunction(FunSpec.builder("main")
    .addParameter("args", String::class, VARARG)
    .addStatement("%T(args[0]).greet()", greeterClass).build())
    .build()
    file.writeTo(System.out)
    ------------------------------------------------------------------------------------------------------
    class Greeter(val name: String) {
    fun greet() {println("""Hello, $name""")}}
    fun main(vararg args: String) {Greeter(args[0]).greet()}
     */
    override fun process(annotations: MutableSet<out TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
        val elementsAnnotatedWith: Set<out Element> = roundEnvironment.getElementsAnnotatedWith(CreateService::class.java);
        elementsAnnotatedWith.forEach { element ->
            //得到包名
            var e = element
            while (e.kind != ElementKind.PACKAGE) {
                e = e.enclosingElement
            }
            val packageName = (e as PackageElement).toString()
            val service = element.getAnnotation(CreateService::class.java)
            val funspecs = mutableListOf<FunSpec>()
            try {
                val apiClass = Class.forName(service.interfaceApi)
                val mapMethod = mutableMapOf<String, String>()
                apiClass.methods.forEach { m ->
                    m.annotations.forEach { an ->
                        if (an.annotationClass.simpleName == PostBody::class.java.simpleName) {
                            mapMethod[m.name] = (an as PostBody).json
                        }
                    }
                }

                apiClass.kotlin.members.forEach { m ->
                    when (m.name) {
                        "equals" -> ""
                        "hashCode" -> ""
                        "toString" -> ""
                        else -> {
                            if (mapMethod.containsKey(m.name)) {
                                val builder: FunSpec.Builder = FunSpec.builder(m.name)
                                val mapParams = gson.fromJson<Map<String, String>>(mapMethod[m.name], object : TypeToken<Map<String, String>>() {}.type)
                                val sb = StringBuilder()
                                sb.append("val map = mutableMapOf<String, Any>()\n")
                                mapParams?.forEach {
                                    sb.append("map["${it.key}"]=${it.key}\n")
                                    when (it.value) {
                                        "String" -> {
                                            builder.addParameter(it.key, String::class.java)//参数名,参数类型
                                        }
                                        "Int" -> {
                                            builder.addParameter(it.key, Int::class.java)//参数名,参数类型
                                        }
                                        "Long" -> {
                                            builder.addParameter(it.key, Long::class.java)//参数名,参数类型
                                        }
                                        "Double" -> {
                                            builder.addParameter(it.key, Double::class.java)//参数名,参数类型
                                        }
                                        "Float" -> {
                                            builder.addParameter(it.key, Float::class.java)//参数名,参数类型
                                        }
                                        "Boolean" -> {
                                            builder.addParameter(it.key, Boolean::class.java)//参数名,参数类型
                                        }
                                        "Short" -> {
                                            builder.addParameter(it.key, Short::class.java)//参数名,参数类型
                                        }
//                                        "String" -> {
//                                            builder.addParameter(it.key, String::class.java)//参数名,参数类型
//                                        }
                                    }
                                }

                                sb.append("val result = service.${m.name}(com.wx.test.api.RequestBodyCreate.toBody(com.google.gson.Gson().toJson(map)))\n")
                                sb.append("return result")
                                builder.addModifiers(KModifier.SUSPEND)
                                    .returns(m.returnType.asTypeName())//获取返回类型
                                    .addStatement(sb.toString())
//                                    .addModifiers(KModifier.OVERRIDE)
                                funspecs.add(builder.build())
                            } else {
                                val builder: FunSpec.Builder = FunSpec.builder(m.name)
                                val sb = StringBuilder()
                                sb.append("return service.${m.name}(")
                                for ((index, p) in m.parameters.withIndex()) {
                                    p.name?.let {
                                        builder.addParameter(it, p.type.asTypeName())//参数名,参数类型
                                        sb.append("${p.name}")
                                        if (index < m.parameters.size - 1)
                                            sb.append(",")
                                    }
                                }
                                sb.append(")")
                                builder.addModifiers(KModifier.SUSPEND)
                                    .returns(m.returnType.asTypeName())//获取返回类型
                                    .addStatement(sb.toString())
//                                    .addModifiers(KModifier.OVERRIDE)
                                funspecs.add(builder.build())
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            val classNameOrigin = service.interfaceApi
            val superClassNameOrigin = service.superClass
            val index = classNameOrigin.lastIndexOf('.')
            val indexS = superClassNameOrigin.lastIndexOf('.')
            val className = classNameOrigin.substring(index + 1 until classNameOrigin.length)
            var greeterClass = "${className}Repository";
            val superClassName = ClassName(superClassNameOrigin.substring(0 until indexS), superClassNameOrigin.substring(indexS + 1 until superClassNameOrigin.length))
            val superInterfaceClassName = ClassName(classNameOrigin.substring(0 until index), className)
            val newSuperClassName = superClassName.parameterizedBy(superInterfaceClassName)
            val typeSpecClassBuilder = TypeSpec.classBuilder(greeterClass)//类名
                .primaryConstructor(//本类默认构造函数
                    FunSpec.constructorBuilder()
//                        .addParameter("retrofit", Retrofit::class)//构造函数里面参数
//                        .addAnnotation(Inject::class.java)//构造函数加注解
                        .build()
                ).superclass(newSuperClassName)//继承的父类
//                .addSuperclassConstructorParameter("retrofit", Retrofit::class)//父类构造函数参数
//                .addSuperinterface(superInterfaceClassName)//父类实现接口
            funspecs.forEach {
                typeSpecClassBuilder.addFunction(it)
            }
            val file = FileSpec.builder(packageName, greeterClass)
                .addType(
                    typeSpecClassBuilder.build()
                ).build()
            mFiler?.let { filer -> file.writeTo(filer) }
        }
        return true
    }

    private fun log(message: String) {
        processingEnv.messager.printMessage(Diagnostic.Kind.NOTE, message)
    }
}

八、查看自动生成的 Repository 类,及调用

  1. build app工程 , 或者点击 app task的 assembleDebug 如下图:
  1. 查看自动生成的类:

  2. 查看生成的代码:

kotlin 复制代码
    package com.wx.kotlin_kapt_demo.data_source.kapt

    import com.wx.kotlin_kapt_demo.data_source.repository.BaseRepository
    import com.wx.test.api.Api
    import com.wx.test.api.`data`.BaiduDataBean
    import com.wx.test.api.`data`.BaseResponse
    import java.util.ArrayList
    import kotlin.Int
    import kotlin.Long
    import kotlin.String
    import okhttp3.RequestBody

    public class ApiRepository : BaseRepository<Api>() {
        public suspend fun get899(
            word: String,
            queryWord: String,
            pn: Int,
            gsm: String
        ): BaseResponse<ArrayList<BaiduDataBean>> = service.get899(word, queryWord, pn, gsm)

        public suspend fun register(
            username: String,
            password: String,
            repassword: String
        ): String = service.register(username, password, repassword)

        public suspend fun testPostBody(body: RequestBody): String = service.testPostBody(body)

        public suspend fun testPostBody222(ID: Long, name: java.lang.String): String {
            val map = mutableMapOf<String, Any>()
            map["ID"] = ID
            map["name"] = name
            val result = service.testPostBody222(com.wx.test.api.RequestBodyCreate.toBody(com.google.gson.Gson().toJson(map)))
            return result
        }
    }
  1. app 工程中调用:
kotlin 复制代码
    class MainVIewModel : ViewModel() {

        private val repository by lazy { ApiRepository() }
        val liveDataImg by lazy { MutableLiveData<String>() }

        fun requestTest() {
            viewModelScope.launch(Dispatchers.IO) {
                val result = repository.get899("西游记", "西游记", 1, "")
                result?.data?.takeIf {
                    it.size > 0
                }?.let {
                    liveDataImg.postValue(it[0].middleURL)
                }
            }
        }
    }

总结:

  1. 本文介绍了注解处理器基础用法
  2. 示例了它在架构中 Repository 类的自动生成
  3. 示例了 Retrofit 中接口类里面get请求,post请求,post body 的简单自动生成写法
  4. 示例了注解处理中 KotlinPoet 的应用

特别感谢参考文章

Android高级进阶系列:注解处理器APT用法详解
Kotlin 注解

点关注,收藏 ,不迷路

感谢阅读:

github地址
gitee地址

相关推荐
miao_zz1 小时前
基于react native的锚点
android·react native·react.js
安卓美女1 小时前
Android自定义View性能优化
android·性能优化
剑海风云2 小时前
Google大数据架构技术栈
大数据·google·架构·bigdata
Dingdangr2 小时前
Android中的四大组件
android
skaiuijing3 小时前
巧用二级指针
c语言·开发语言·算法·架构·操作系统
mg6683 小时前
安卓玩机工具-----无需root权限 卸载 禁用 删除当前机型app应用 ADB玩机工具
android
安卓机器3 小时前
Android架构组件:MVVM模式的实战应用与数据绑定技巧
android·架构
码龄3年 审核中4 小时前
MySQL record 05 part
android·数据库·mysql
服装学院的IT男4 小时前
【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树
android
技术无疆5 小时前
Hutool:Java开发者的瑞士军刀
android·java·开发语言·ide·spring boot·gradle·intellij idea