关于注解,你需要知道的一些知识:
Annotation注解(一)- 基础
Annotation注解(二)- 进阶 - 自定义ButterKnife

这篇博客,主要讲解关于注解的一些基本知识,包括注解的概念、分类、作用,常见注解的定义及其解析方式等。

尊重原创,转载请注明出处 https://segmentfault.com/a/11...
本文出自 强哥大天才的博客

Annotation的概念

1. 概念

关于Annotation注解的概念,我们可以看下官方的解释:

Annotations, a form of metadata;
provide data about a program that is not part of the program itself.
Annotations have no direct effect on the operation of the code they annotate

Annotation注解是Java中的一种元数据,它可以往源代码中添加额外的数据,并且对注解的代码无直接影响;包名、类、成员方法、成员属性、参数都可以被注解。

2. 作用

注解可以让我们的代码更加简洁,并且增加代码的复用性,避免冗余代码。
如果细分起来,注解主要有3个作用:

  • 编译时标记:在编译时,通过标记代码,来提示信息、检查错误
  • 编译时处理:在编译时,通过构建代码,来动态生成一些额外的代码、或者Java/XML等文件
  • 运行时处理:在运行时,我们可以动态的获取注解信息

3. 分类

1. 标准Annotation
标准Annotation是指Java自带的Annotation。
eg:@Override[重写]、@Deprecated[不鼓励使用]、@SuppressWarnings[忽略警告]等。

2. 元Annotation
元Annotation是指注解Annotation的Annotation。
eg:@Retention, @Target, @Inherited, @Documented等。

3. 自定义Annotation
我们可以利用元Annotation来自定义一些自己的Annotation。

元Annotation

元Annotation是指注解Annotation的Annotation。

  • @Documented:Annotation是否会保存到JavaDoc文档中
  • @Inherited:Annotation是否可以被继承,默认是false
  • @Target(ElementType[] types):Annotation可以用来注解哪些元素

ElementType:元素类型,比如类、方法、变量、参数等
clipboard.png

  • @Retention(RetentionPolicy ploicy):Annotation的保留策略

RetentionPolicy.SOURCE:Annotation仅保留在java源码中
- 大都为Mark Annotation,做一个警示、校验的作用
RetentionPolicy.CLASS:Annotation保留在java源码、class文件中
- 默认的保留策略,一般配合Processor注解处理器,在构建时动态生成代码
RetentionPolicy.RUNTIME:Annotation保留在java源码、class文件、运行时
- 与CLASS的区别是,这类Annotation在运行时会被加载到JVM中,因此我们可以在程序运行的过程中动态获取到Annotation的相关信息
clipboard.png

自定义Annotation

我们先来看一下一个最为基础的Annotation示例代码(这是一个自动生成类头信息的一个注解)

调用

@ClassInfo (
   author = "xzqbetter@163.com",
   date = "11/17/2017",
   currentRevision = 6,
   reviewers = {"Alice", "Bob", "Cindy"}    // Note array notation
)
public class Test1 {
    // class code goes here
}

定义

public @interface ClassInfo {
   String author();
   String date();
   int currentRevision() default 1;
   String[] reviewers();                    // Note use of array
}

解释

1.注解的定义:可以看到,我们利用@interface,定义了一个注解类,之后就可以在代码中使用这个注解

  • @interface是interface的一种变异形式

2.参数的定义:在注解类内部,我们声明了很多抽象方法,来定义注解所需的参数

  • 对应关系:方法名 - 参数名,方法的返回值 - 参数类型
  • 返回值的限制:只能是基本类型、String、Class、Enumeration、Annotation及其一维数组
  • 方法的限制:这些方法没有方法体、没有参数、不能抛异常,并且只能用public abstract进行修饰
  • 可以用default来指定默认值
  • 如果只有一个参数,可以用value()代替(在使用的时候无需写参数名)
  • 可以利用classInfo.author()来获取参数值

解析Annotation

完成自定义Annotation后,我们还需要知道,针对这些注解,我们要做哪些相关的处理,这就涉及到了Annotation的解析操作。
解析Annotation,通常分为:对运行时Annotation的解析、对编译时Annotation的解析;
解析Annotation,其实就是如何从代码中找到Annotation,通常我们的做法是:

  • 反射的方式获取Annotation,运行时Annotation的解析方式
  • 借助apt工具获取Annotation,编译时Annotation的解析方式
  • 另外如果我们需要生成额外的代码、文件,则还需要借助JavaPoet API

反射

反射的解析方式,通常运用在运行时Annotation的解析。
反射是指:利用Class、Field、Method、Construct等reflect对象,获取Annotation:

  • field.getAnnotation(Annotation.class):获取某个Annotation
  • field.getAnnotations():获取所有的Annotation
  • field.isAnnotationPresent(Annotation.class):是否存在该Annotation

这样,我们就可以在程序运行过程中,动态的获取Annotation的信息。

借助反射进行解析,这在一定程度上会影响程序性能

APT工具

概念

APT:是一个注解处理工具 Annotation Processing Tool
作用:利用apt,我们可以找到源代中的注解,并根据注解做相应的处理

  • 根据注解,生成额外的源文件或其他文件
  • 编译生成的源文件和原来的源文件,一起生成class文件

利用APT,在编译时生成额外的代码,不会影响性能,只是影响项目构建的速度

Android Studio配置

使用apt工具前,需要对gradle做一些基本的配置:
1.在project的build.gradle中,添加apt的依赖

dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

2.在module的build.gradle中,使用apt插件

apply plugin: 'com.neenbedankt.android-apt'

JavaPoet

JavaPoet:是一个自动生成Java代码的Java API。
在使用前,我们需要导入相关的依赖:

compile 'com.squareup:javapoet:1.7.0'

JavaPoet主要有以下几个关键类:

  • JavaFile:生成Java文件
  • TypeSpec:定义类
  • MethodSpec:定义方法

MethodSpec

MethodSpec主要用来定义方法。

MethodSpec mainSpec = MethodSpec.methodBuilder("main")         // 方法名
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)            // 修饰符
    .addParameter(String[].class, "args")                      // 参数
    .returns(TypeName.VOID)                                    // 返回值
    .addStatement("$T.out.println($S)", System.class, "Hello,World!!")  // 具体代码
    .build();

// 添加代码的方法
mainBuilder.addCode("System.out.println(\"Hello,World!\")");
mainBuilder.addStatement("$T.out.println($S)",System.class,"Hello, World!");
mainBuilder.addStatement("activity.$L= ($T) activity.findViewById($L)",
          element, ClassName.get(member.asType()), injectViewAnno.value() );
// 这里的element表示一个成员变量, injectViewAnno是一个Annotation, value是注解的参数值

方法名:methodBuilder()
修饰符:addModifiers(Modifier)
返回值:return(TypeName)
参数:addParameter(Class, name)
添加代码:addStatement末尾会自动添加换行符,addCode末位不会自动添加换行符
-$T:表示需要import的类
-$S:表示字符串,会自动添加双引号
-$L:表示变量,不带双引号

TypeSpec

TypeSpec主要用来定义类。

// 外部类
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")         // 类名
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)              // 修饰符
    .addMethod(mainSpec)                                        // 方法
    .build();
// 内部类
TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("innerClass")    // 内部类的类名
    .addSuperinterface(OutClass.class)                              // 外部类的Class
    .build();

JavaFile

JavaFile是真正生成Java文件的核心类。

try {
    JavaFile javaFile = JavaFile.builder("com.example.seven", typeSpec).build();    // 指定包名+类
    javaFile.writeTo(processingEnv.getFiler());                                     // 固定写法
} catch (IOException e) {
    e.printStackTrace();
}

运行时Annotation

运行时Annotation,是指@Retention为RetentionPolicy.RUNTIME的注解。
对于这类注解,我们通常配合反射进行解析。

运行时Annotation的解析,需要借助反射进行解析,这在一定程度上会影响程序性能。
早期的依赖注入框架,大都属于运行时处理。

示例代码

public static void main(String[] args) {
    try {
        Class clazz = Class.forName("com.example.Runtime");
        for (Method method : clazz.getMethods()) {
            MethodAnno methodAnno = method.getAnnotation(
MethodAnno.class);
            if (methodAnno != null) {
                System.out.println("method name:" + method.getName());
                System.out.println("method author:" + methodAnno.author());
                System.out.println("method version:" + methodAnno.version());
                System.out.println("method date:" + methodAnno.date());
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

编译时Annotation

编译时Annotation,是指@Retention为RetentionPolicy.CLASS的注解。
对于这类注解,我们通常借助apt工具注解处理器Processor进行解析。

编译时Annotation,利用apt工具,在编译时,自动生成一些额外的代码,相比运行时Annotation更加高效。

定义一个编译时Annotation的标准流程如下:

  1. 定义注解:@interface
  2. 定义注解处理器:重写AbstractProcessor的2个方法
  3. 实现注解处理器:JavaPoet
  4. 注册注解处理器:@AutoService
  5. 使用注解,build工程

下面,我们将一一进行讲解

1. 定义注解

参考,上面的【自定义注解】部分

2. 定义注解处理器

概念

注解处理器是一个继承自AbstractProcessor的一个Java类,内部主要定义了:

  • 要处理的注解是谁:通过getSupportedAnnotationTypes方法进行指定
  • 如何处理这个注解:通过process方法进行指定

注意:AbstractProcessor只能用在Java项目中,在Android项目中无法使用,因为Android内部删除了这个类;因此如果想要在Android项目中,使用这个类,需要新建一个Java Library库。

示例代码

@AutoService(Processor.class)
public class AutoCreateProcessor extends AbstractProcessor {
    
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 返回的Set集合,是注解类ClassName的集合
        // AutoCreate是一个自定义的Annotation
        return Collections.singleton(AutoCreate.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 利用JavaPoet进行相关处理
        return false;
    }
    
}

解释

getSupportedAnnotationTypes

  • 这个方法主要用来指定,当前的注解处理器,需要处理哪些Annotation
  • 这些Annotation,将会通过一个Set集合的形式返回
  • Set集合内部存储的是,所有注解的类名className

process

  • 这个方法主要用来定义,针对这些注解,我们要做哪些相应的处理
  • 通常,我们会在方法内部,利用JavaPoet创建相关的Java类、或是XML文件
  • 返回值表示,当前Processor是否接收这组Annotation,如果接收,后续其他的Processor将不会继续处理

3. 实现注解处理器

实现注解处理器,主要是利用JavaPoet对process部分的代码进行完善补充,在下一个博客中,我们将会详细介绍。

4. 注册注解处理器

注册注解处理器,就是告诉APT这个类是一个Annotation Processor
我们可以借助AutoService进行快速注册:

  • 添加AutoService的依赖
  • 在类上注解 @AutoService(Processor.class),完成注册
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)

5. build工程

完成以上步骤,我们build下工程,就会在build目录下自动生成对应的java/class文件

  • java文件在build/generated文件夹下
  • class文件在build/intermediates文件夹下

clipboard.pngclipboard.png


强哥大天才
6 声望1 粉丝

引用和评论

0 条评论