kotlin编写编译时注解

1.定义注解

As里新建一个Java Library module,必须是Java Library Module
在这里插入图片描述

此处命名为route-api

该module存放一些与纯java类相关的文件

1. 定义一个注解 Route
/**
* @param name 路由用的跳转名字,该key与一个.Class关联,若不填写默认为Activity类名去掉“Activity”,如MainActivity为”Main“
* @param appCode app跳转协议码,为后台或前端调用本地页面跳转的协议如“app://10000"跳转到登录页
* @param interceptors 拦截器,用于跳转到当前目的页面前先判断某些条件是否成立,若不成立则拦截跳转到其他页面,
* 如,某些页面需要登录才能跳转到该页面则添加一个登录的拦截器,如果没有登录则跳转到登录页,反之,则跳转到目的页面
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Route(val name: String = "",
                    val appCode: Int = -1,
                    val interceptors: Array<KClass<*>> = emptyArray())

该注解用于标记该类自动配置到路由表中

2. 定义一个Config类用于配置自动生成类的包名,类名等
object RouteConfig {
   var routePackName = "com.tugo.decor"
   var routeSimpleName = "AutoRouter"
   var registerMethod = "registeredToRouter"
}

2.实现AbstractProcessor

另外新建一个java Library Module,命名为route-process

1.建立一个TypeElement处理类
class RouteAnnotationInfo(val element: TypeElement) {

    //全限定名,如com.tugou.andromeda.tgkit.MainActivity
    val targetQualifiedName = element.qualifiedName.toString();
    //简单名,如MainActivity
    val targetName = element.simpleName.toString()

    //拦截器类集合
    val interceptors: List<ClassName>
    //Route注解类的变量,用于获取某类的Route注解
    val annotation: Route

    init {
        annotation = element.getAnnotation(Route::class.java)
        interceptors = iniInterceptors()
    }

    //同Route的name
    val name: String
        get() {
            return if (annotation.name.isBlank()) {
                targetName.replace("Activity", "");
            } else {
                annotation.name
            }
        }
    //同Route的univCode
    val univCode: Int = annotation.univCode

    /**
     * 用于初始化拦截器的类,因为编译时无法获取Class文件,只好通过字符串获取拦截器的类的全限定名以获得其包名和类名,其他方法可参看 https://www.cnblogs.com/fuckingaway/p/6703021.html,感觉上面写的太麻烦,我就没有细看
     * 然后将其转为ClassName对象,ClassName类为javapoet包下的声明Class的类,用其可以自动导入包名 ,关于javapoet介绍可看
     * @see <a href=" https://www.2cto.com/kf/201609/543893.html ">javapoet——会写代码的“诗人”</a>
     */
    fun iniInterceptors(): List<ClassName> {
        val resultList = ArrayList<ClassName>();
        for (str in getInterceptorStrs(element.getAnnotation(Route::class.java))) {
            if (str.isNotBlank()) {
                resultList.add(generateClassName(str))
            }
        }
        return resultList;
    }

}

/**
 *用于将字符串类型的去限定类类名转换为ClassName类
 */
fun generateClassName(str: String): ClassName {
    val lastIndex = str.lastIndexOf(".")
    val pack = str.substring(0, lastIndex)
    val simpleName = str.substring(lastIndex + 1)
    return ClassName.get(pack, simpleName)
}

/**
 *用于获取注解处理器类的字符串
 */
fun getInterceptorStrs(annotation: Route): List<String> {
    val strs = annotation.toString()
            .substring(Route::class.java.name.length + 1)
            .replace("(", "").replace(")", "")
            .split(", ")
    for (s in strs) {
        if (s.contains("interceptors=")) {
            return s.substring("interceptors=".length).split(",")
        }
    }
    return emptyList()
}
2.建一个RouteProcess类继承成AbstractProcessor
@AutoService(Processor::class)
class RouteProcess : AbstractProcessor() {
	/**
     *该方法用于表明该处理器处理那些注解
     */
    override fun getSupportedAnnotationTypes(): Set<String> {
        return setOf<String>(Route::class.java.getCanonicalName())
    }
    /**
     *该方法用于处理注解,并生成代码
     */
    override fun process(set: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
        //声明一个Set集合用于储存处理类
        val hashSet = HashSet<RouteAnnotationInfo>();
        //获取所有被Route注解的元素
        val elements = roundEnv.getElementsAnnotatedWith(Route::class.java)
        for (annotatedElement: Element in elements) {
            if (annotatedElement.kind != ElementKind.CLASS) {
                processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "该注解只能用于类", annotatedElement)
                return false
            }
            val typeElement = annotatedElement as TypeElement
            hashSet.add(RouteAnnotationInfo(typeElement))

        }
        //准备写代码
        //ParameterizedTypeName用于参数化类如List<T>,下面代码用于声明一个List<TGPage>类
        val routeTypeList = ParameterizedTypeName.get(List::class.java, TGPage::class.java)//此处TGPage为定义路由表单项的一个类

        val methodProvidePagesBuilder = MethodSpec.methodBuilder("providePages")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(List::class.java)
        //下面生成代码为List<TGPage> list=new ArrayList<>()
                .addStatement("\$T list = new \$T<>()", routeTypeList, ArrayList::class.java)

        //"开始写方法内容"
        for (routeAnnotationInfo in hashSet) {

            methodProvidePagesBuilder.addCode("list.add(new \$T(\$S,\$T.class",
                    TGPage::class.java,
                    routeAnnotationInfo.name,
                    ClassName.get(routeAnnotationInfo.element)
            )

            //生成Class数组的代码
            val codeBlock = CodeBlock.builder();
            codeBlock.add(",\$L", routeAnnotationInfo.univCode)

            if (routeAnnotationInfo.interceptors.isNotEmpty()) {
                codeBlock.add(",new \$T[]{", Class::class.java)
                for (clazz in routeAnnotationInfo.interceptors) {
//                methodProvidePagesBuilder.addComment("测试element:\$L",str)
                    if (routeAnnotationInfo.interceptors.indexOf(clazz) != 0) {
                        codeBlock.add(",")
                    }
                    codeBlock.add("\$T.class", clazz)
//                    methodProvidePagesBuilder.addComment("测试element:\$L", clazz)
                }
                codeBlock.add("}")
            }
            methodProvidePagesBuilder.addCode(codeBlock.build())
                    .addStatement("))")
        }

        methodProvidePagesBuilder.addStatement("return list")

        val registe = MethodSpec.methodBuilder(RouteConfig.registerMethod)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addStatement("\$T.registerPages(\$N())", ClassName.get(RouteConfig.tgRouterPackName, RouteConfig.tgRouterSimpleName), "providePages")
                //tgRouterPackName和tgRouterSimpleName为路由跳转的类,此处请忽略,该文只介绍如何自动生成路由表
                .build()
        //"开始写类"

        val routeTable = TypeSpec.classBuilder(RouteConfig.routeSimpleName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(methodProvidePagesBuilder.build())
                .addMethod(registe)
                .build();

        //"写成java文件"
        val javaFile: JavaFile = JavaFile.builder(RouteConfig.routePackName, routeTable)
                .build();
        try {
            //仅调用此方法会报错但不影响生成代码,但会值中断Run,所以用try—catch块包裹住
            javaFile.writeTo(processingEnv.filer)
        } catch (e: IOException) {
            e.printStackTrace();
        }
        return false
    }
}    

MethodSpec用于是用于方法生成的类,通过MethodSpec.methodBuilder(methodName)生成一个MethodSpec的构造器,可以用构造方法

build.addModifiers用于声明方法的修饰符如public static private等等,可以通过Modifier的枚举传入

build.returns(Type returnType)声明方法的返回类型,未声明的则为void

build…addComment(String commen)用于生成注释,当你的注解器可以自动生成代码时,你可以用其来进行测试数据

build.addStatement(String format, Object… args)为方法增加一行代码,会自动添加分号和换行

  • format支持字符串模板,同过 T 或 T或 TS, L , L, L,N来表示一个占位,args则为前面占位的实际值,args的个数要与前面占位符的个数相同

  • T 表 示 T y p e 类 型 − − 类 或 接 口 等 类 型 的 占 位 符 , 对 象 可 以 传 . c l a s s 对 象 或 C l a s s N a m e 对 象 等 , 会 对 其 自 动 导 包 , 注 意 如 果 是 M a i n A c t i v i t y . c l a s s , 生 成 的 代 码 为 M a i n A c t i v i t y 多 以 如 果 你 需 要 生 成 M a i n A c t i v i t y . c l a s s 你 需 要 在 T表示Type类型--类或接口等类型的占位符,对象可以传.class对象或ClassName对象等,会对其自动导包,注意如果是MainActivity.class,生成的代码为MainActivity多以如果你需要生成MainActivity.class你需要在 TType.classClassNameMainActivity.classMainActivityMainActivity.classT后面加上.class字符串即“$T.class”

  • S 代 表 字 符 串 类 型 的 占 位 符 , 对 象 必 须 为 字 符 串 类 ; S代表字符串类型的占位符,对象必须为字符串类; SL表示文字类型的字符串,包含字符串类型,也可以支持整数等类型,具体请自己尝试

  • N 表 示 方 法 名 类 型 的 占 位 符 , 注 意 只 会 生 成 方 法 的 名 字 , 方 法 调 用 要 自 己 加 ( ) , 如 “ N表示方法名类型的占位符,注意只会生成方法的名字,方法调用要自己加(),如“ N(),N()”,“Main”,才表示调用了Main()

    上面几个可参考 https://blog.csdn.net/qxs965266509/article/details/53390860

build.add(String format, Object… args)同上面类似,但不会换行也不会添加分号

CodeBlock是生成代码片段的类,方法等同MethodSpec的方法相同

TypeSpec用于生成类,接口等,TypeSpec.classBuilder(String name)可以生成一个TypeSpec的Builder 对象,该Builder 对象可以用于设置类的生成

build.addModifiers(Modifier.PUBLIC, Modifier.FINAL)用于声明类的修饰符
build.addMethod(methodProvidePagesBuilder.build())用于为类添加方法,参数为MethodSpec类型

JavaFile为用于生成.java文件JavaFile.builder(String packageName, TypeSpec typeSpec),packageName为该java文件的包名,typeSpec则为该java文件的class

javaFile.writeTo(processingEnv.filer)则真正用于将文件写入到磁盘中

3.注意

1.要将注解处理器建立在JavaLibrary Module里

Android的Library module不支持javax包下的文件,而我们需要的AbstractProcessor确实javax包下的

2.auto-service使用,并用kapt进行注解处理

auto-service用于注册处理器,compiler库会生成一个jar,在jar中META-INF/services目录需要新建一个特殊的文件javax.annotation.processing.Processor,文件里的内容就是声明你的处理器,而auto-service可以自动生成META-INF/services/javax.annotation.processing.Processor文件

注意在kotlin中要用kapt "com.google.auto.service:auto-service:1.0-rc2"声明@AutoService的直接处理器

可参看博客:http://yeungeek.com/2016/04/27/Android公共技术点之二-Annotation-Processing-Tool/?utm_source=tuicool&utm_medium=referral

使用kapt后,Auto-Service没有在resources下生成注册注解处理器的文件时看博客: https://www.jianshu.com/p/b58d733bc54ehttps://my.oschina.net/zzxzzg/blog/1558890

关于注解处理器调试可看该博客 https://blog.csdn.net/tomatomas/article/details/53998585#comments ,虽然我这表没有成功,不知道是不是kotlin的原因

附:javapoet一些类的解释 https://blog.csdn.net/wzgiceman/article/details/54580745

JavaPoet的基本使用 https://blog.csdn.net/crazy1235/article/details/51876192

JavaPoet中文文档 http://tool.oschina.net/uploads/apidocs/jdk-zh/javax/lang/model/element/Element.html

英文文档 https://docs.oracle.com/javase/7/docs/api/

Kotlin编码窍门之注解 https://www.jianshu.com/p/d3721b171e9f

Java Annotation Processor 小记 http://lotabout.me/2017/Notes-on-Java-Annotation-Processor/

Getting Class values from Annotations in an AnnotationProcessor https://area-51.blog/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/

注解解析器 ---- Element https://blog.csdn.net/qq_26376637/article/details/79496351

注解 https://jackchan1999.github.io/2017/04/30/java/注解/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值