注解处理器在架构,框架中实战应用: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地址

相关推荐
等一场春雨16 分钟前
领域驱动设计(DDD)四 订单管理系统实践步骤
架构
五味香1 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录2 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽4 小时前
Android实训九 数据存储和访问
android
aloneboyooo4 小时前
Android Studio安装配置
android·ide·android studio
moton20174 小时前
云原生:构建现代化应用的基石
后端·docker·微服务·云原生·容器·架构·kubernetes
Jacob程序员4 小时前
leaflet绘制室内平面图
android·开发语言·javascript
2401_897907865 小时前
10天学会flutter DAY2 玩转dart 类
android·flutter
你板子冒烟了5 小时前
JJJ:arm64架构下的asid相关
架构
m0_748233645 小时前
【PHP】部署和发布PHP网站到IIS服务器
android·服务器·php