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或 T或S, 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你需要在 T表示Type类型−−类或接口等类型的占位符,对象可以传.class对象或ClassName对象等,会对其自动导包,注意如果是MainActivity.class,生成的代码为MainActivity多以如果你需要生成MainActivity.class你需要在T后面加上.class字符串即“$T.class”
S 代 表 字 符 串 类 型 的 占 位 符 , 对 象 必 须 为 字 符 串 类 ; S代表字符串类型的占位符,对象必须为字符串类; S代表字符串类型的占位符,对象必须为字符串类;L表示文字类型的字符串,包含字符串类型,也可以支持整数等类型,具体请自己尝试
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的直接处理器使用kapt后,Auto-Service没有在resources下生成注册注解处理器的文件时看博客: https://www.jianshu.com/p/b58d733bc54e ,https://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