引子
作为Java / Android 开发者,我们平时开发中经常用到注解,有的方便了我们开发,动态的添加代码,减少我们的开发量;有的编译时能友好的提示我们,帮我们纠错。
现在我们就回炉,看看注解相关的问题
注解基础
Java 内置注解
Java 在jdk中,定义了一套注解,我们最常见的有:
- @Override
- @Deprecated
- @SuppressWarnings
这3个就不用多说了,一看都懂。
那么,他们是分别什么时候起作用呢,这就要说说注解的保留策略了(RetentionPolicy)
我们来看看源码中是怎么定义的:
分别是 源码级、编译时、运行时 三种策略:
RetentionPolicy | 含义 |
---|---|
SOURCE | 仅存在Java源文件,经过编译器后便丢弃相应的注解 |
CLASS | 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释 |
RUNTIME | 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解 |
然后,我们也知道源码级框架是在javac编译源码时,生成框架代码或文件。源码级别框架发生过程是在编译期间,并不会过多影响到运行效率
上面提到的3个最常见的注解,前两个是源码级注解,开发过程中提示我们,后一个是运行时注解,告诉VM忽略警告
元注解
Java 中的元注解,也就是 注解的注解,有4个:
- @Retention - 上面提到的保留策略
- @Documented - 标记这些注解是否包含在用户文档中。
- @Target - 修饰对象范围:对象、方法。。。
- @Inherited - 也是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的
我们再看看 @Target, 它的定义里,value元素是一个枚举:
通过这个 @Target, 来标识注解作用在什么类型上
我们在定义一个注解时,就会用到元注解。像jdk提供给我们的注解,也是由他们而来的
自定义注解
在自定义个注解时,会自动继承java.lang.annotation.Annotation接口。
我们可以看到:
实现一个简单的注解demo:
定义起来也比较简单易懂。其中有一些规则,我们要清楚:
注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
定义一个注解的工作就完成了,那谁来读取、处理注解呢,下面就来看看注解处理器
注解处理器
JDK 提供一些相关的api, 如: AnnotatedElement,通过反射也可以获取注解相关的内容,进行一些操作
APT & AnnotationProcessor
APT(Annotation Processing Tool)是一个命令行工具,它对源代码文件进行检测找出其中的annotation后,使用annotation processors来处理annotation。
APT 也经过了几个版本的迭代,现在也不需要单独运行apt这样的命令行工具,Java编译器本身就可以完成对注解的处理。
直接继承于javax.annotation.processing.AbstractProcessor进行相应的实现。
对应Android开发者来说,apt 也被out了,Android Studio Gradle 2.2 版本以上,建议用 AnnotaitionProcessor。Gradle 也支持了annotationProcessor
自定义AnnotationProcessor
项目搭建
- 建立2个Java Library,分别为:setting_annotation,setting_annotation_processor
- setting_annotation 中定义注解,不需要添加特别的依赖库
- setting_annotation_processor 中定义自己的的AnnotaitionProcessor,实现下注解处理逻辑。build.gradle中需要添加:
|
|
用于我们后面使用。
4.项目依赖是这样的:
贴上 具体的配置,不多说了。
app 的 gradle 配置:
|
|
setting_annotation_processor的gradle配置:
Demo 示例
我们来实现一个 自动生成 JavaBean 的例子:
- 注解定义(setting_annotation 中)
|
|
- 注解使用(app 中)
|
|
- 注解处理(setting_annotation_processor 中)
主要实现 AbstractProcessor 的 init()、process()、getSupportedAnnotationTypes()方法
主要思路,在init()中拿到messager、filer相关的工具类,process()中是真正实现注解处理的地方,这里会使用到javapoet的工具类,方便操作Java代码的生成。getSupportedAnnotationTypes()描述注解处理器需要处理的注解,我们新建一个set添加我们的注解即可。
javaopet的操作可以查阅 https://github.com/square/javapoet
完成这些内容后,我们需要配置 processor 在META-INF/services中,我们就不麻烦了,直接用 google 提供的工具类,自动生成,也是一个注解 @AutoService 标记到类上:
|
|
- 验证
我们 run 起来,首先观察,setting_annotation_processor 这个module下的产物:
META-INF下的文件自动生成了
我们再看看,app module 下:
PeopleBean 自动生成了。
OK,大功告成。
说明:注解处理器是运行它自己的虚拟机JVM中。javac启动一个完整Java虚拟机来运行注解处理器。
所以可以使用任何你在其他java应用中使用的东西。可以使用 guava 中的方便的api,处理注解操作
那么类自动生成了,那我们就可以动态的在代码中 去加载,应用了。ButterKnife 不就是这种思路吗?
如何debug注解处理器
前面介绍了大部分过程,再讲一个小技巧,debug注解处理器。
开发过程中少了debug,岂不是很麻烦了,光靠打日志比较浪费时间。Annotation Processor debug 起来还是有点特殊的。
配置远程调试
配置gradle.properties
|
|
开启守护进程
开始debug
- 执行一次 clean project(每次debug前,clean下项目)
打断点,并点击debug按钮
最后,make或者rebuild下,就可以进入 debug 状态了
注意:如果守护task 执行失败,建议重启 stuido 试试
附录
Demo地址:
https://github.com/fenglincanyi/AndroidProject/tree/master/AnnoationProcessorDemo
注解:
http://linbinghe.com/2017/ac8515d0.html
注解处理器:
https://medium.com/@iammert/annotation-processing-dont-repeat-yourself-generate-your-code-8425e60c6657
https://gist.github.com/iammert/c9da4150a5b5faa4b7bd8fe7915d6e6b
https://www.race604.com/annotation-processing/
javaPoet:
https://juejin.im/entry/58fefebf8d6d810058a610de
https://xsfelvis.github.io/2016/11/06/%E7%BC%96%E8%AF%91%E6%9C%9F%E6%B3%A8%E8%A7%A3%E4%B9%8BJavaPoet/
Debug annoation processor
http://www.jianshu.com/p/80a14bc35000