注解那些事儿!
引言
在java开发中你是否留意到随处可见的以下代码
是否习以为常,认为都是系统自动添加的就忽略它们。
为什么在发现代码有警告时,加上@SuppressWarnings后警告就消失了?
引用某些三方库后,又会出现Java中从未见过的注解。只有按照API说明的方式编写注解信息,才能正常运行,为什么?
本篇就来聊聊注解那些事!
注解
什么是注解
注解的定义首先明确注解也是类的一种,同class、interface,也会生成注解对象,只不过生成的注解对象依附在注解的对象上,不易察觉
定义注解的关键字是@interface
注解的作用
它的作用就是标记元素
何为元素?
代码中可见的有实际意义的单元即元素
元素有哪些
对应关系以上是常见元素的对应关系图
既然是标记,就会有标记时长,不然就成永久标记,不符合设计思想有始有终
使用周期
一般类的使用周期:
- 定义类A,并编辑为A.java(编辑时)
- 通过javac将A.java编译成A.class(编译时)
- JVM加载A.class至内存空间,用以生成对象(运行时)
而注解的使用周期是可定义的,通过@Retention
关键字,定义注解的使用周期
不同的周期声明,注解存在的位置也不同
编辑时(SOURCE):仅存在于源文件
编译时(CLASS):存在于源文件和字节码文件
运行时(RUNTIME):存在于源文件、字节码文件及JVM内存
为什么@Retention能够修饰注解,它自己不是注解吗?
元注解
这里就要引出元注解的概念,修饰注解的注解称为元注解。
用来声明注解所要标记的元素类型、使用周期等信息
元注解的目标元素类型为ANNOTATION_TYPE即注解类型
有了注解类型和周期,那注解标记对象这一行为有什么意义呢?
标记的作用
-
代码审查
编辑阶段检查代码是否有逻辑上的错误
如开头的@SuppressWarnings,IDE发现代码警告后,通过添加@SuppressWarnings注解在编辑阶段忽略警告;
又如Android中的IdRes,指明变量必须是R中的ID,否则编译阶段会出警告;
@Nullable@NonNull也是对编辑阶段逻辑的检查。 -
配置功能
通过对各类型元素添加注解,能够给元素赋予更多的信息
如Retrofit通过注解的形式为请求设置参数
针对配置功能,有两种截然不同的处理方式:
- 运行时处理:通过反射,获取元素上依附的注解对象,根据注解对象的参数做相应处理(Android中ViewInject框架就是采用此处理方式)
- 编译时处理:通过注解处理器,扫描出要处理的注解,根据需要处理
这里来看看编译时处理是怎样应用注解的
注解处理器
注解的要求
因为处理的时机是在编译时,所以注解的使用周期最少也要坚持到编译时,这也就要求了注解处理器所处理的注解必须有如下声明
处理器的要求
- 统一处理格式
注解不止是一家定义使用的,Java有它自己的注解,Retrofit这种三方库也有自己定义的注解。注解的定义规范上述已经有了,那处理注解也要有一套规范,这就是AbstractProcessor。
所有自定义的注解处理器都必须继承自AbstractProcessor,实现其方法处理注解
- 添加注册信息
注解处理器实际也是个类,那编译器在成百上千的类面前先执行谁呢?思考下Java程序的启动,它会找出实现了main方法的类作为程序入口,以此来运行整个程序逻辑,这是规定死的。同样注解处理器也要被明确优先执行,以防使用注解时发现注解还没有被处理。
- Last But Not Least
由于AbstractProcessor类在Java包中,所以自定义的注解处理器仅能在Java模块下编写,在Android中就是Java Library,否则无法引用AbstractProcessor类。
注册
-
手动注册
在main目录下与java的同级的resources目录中,增加文件META-INF.services.javax.annotation.processing.Processor
,添加内容为自定义注解处理器的包路径 -
自动注册
添加Google的com.google.auto.service:auto-service:1.0-rc4
依赖,在自定义注解处理器类上添加注解
处理
注解有了也注册了注解处理器,接下来注解处理器便开始工作
明确工作范围
注解处理器需要明确当前工作环境Java的版本,注解是Java1.5才引入的概念,所以注解处理器肯定在此之后。
常规写法返回最新支持的版本,既然是重写,看看父类
AbstractProcessor父类方法中获取@SupportedAnnotationTypes的注解对象,取其值作为支持的Java的版本,因此也可以这么写,替代重写getSupportedSourceVersion方法
明确观察对象
注解处理器不是处理所有的注解,它只关心它所关心的
重写getSupportedAnnotationTypes方法,返回需要处理的注解类型
既然是重写,看看父类
AbstractProcessor与getSupportedSourceVersion方法相同的设计模式,可以通过注解设置观察对象
注意:重写优先于注解
process
工作条件已经明确了开始工作!
process方法是主要处理方法,共两个参数:set中存储着将要处理的注解类型,就是getSupportedAnnotationTypes所返回的注解类型、roundEnvironment理解为运行环境,可以获取程序运行中的信息。
以仿ButterKnife的代码为例
注解的很清楚了,找出被BindView注解的变量,取其上注解对象的值,并和对象一起存到MainActivity唯一确定的proxy对象中
有了注解配置的所有信息,接下来就可以为所欲为了
直接返回Java字符串,生成的.java文件内容就是字符串内容
这种生成Java文件的方法很容易出错, square公司的JavaPoet库是很好的选择,具体用法在GitHub上有官方介绍