美文网首页知识点
注解那些事儿!

注解那些事儿!

作者: s1991721 | 来源:发表于2019-03-12 21:57 被阅读29次

    引言

    在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上有官方介绍

    相关文章

      网友评论

        本文标题:注解那些事儿!

        本文链接:https://www.haomeiwen.com/subject/dxdolqtx.html