注解那些事儿!

引言

在java开发中你是否留意到随处可见的以下代码

是否习以为常,认为都是系统自动添加的就忽略它们。

为什么在发现代码有警告时,加上@SuppressWarnings后警告就消失了?

引用某些三方库后,又会出现Java中从未见过的注解。只有按照API说明的方式编写注解信息,才能正常运行,为什么?

本篇就来聊聊注解那些事!

注解

什么是注解

注解的定义

首先明确注解也是类的一种,同class、interface,也会生成注解对象,只不过生成的注解对象依附在注解的对象上,不易察觉

定义注解的关键字是@interface

注解的作用

它的作用就是标记元素

何为元素?

代码中可见的有实际意义的单元即元素

元素有哪些

对应关系

以上是常见元素的对应关系图

既然是标记,就会有标记时长,不然就成永久标记,不符合设计思想有始有终

使用周期

一般类的使用周期:

  • 定义类A,并编辑为A.java(编辑时)
  • 通过javac将A.java编译成A.class(编译时)
  • JVM加载A.class至内存空间,用以生成对象(运行时)

而注解的使用周期是可定义的,通过@Retention关键字,定义注解的使用周期

RetentionPolicy

不同的周期声明,注解存在的位置也不同

编辑时(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上有官方介绍

你可能感兴趣的:(注解那些事儿!)