---
注解
---

#### 目录

1. 思维导图
2. 定义
3. 元注解
   - @Retention
   - @Target
   - @Inherited
   - @Documented
   - @Repeatable
4. 自定义注解
   - 运行时注解
     - 反射解析
   - 编译时注解
     - APT 解析
     - 运行原理
5. 常用注解
   - Android 注解
   - Java 注解
6. 参考

#### 思维导图

![](https://i.loli.net/2019/01/29/5c501a8a592e2.png)

#### 定义

注解是 JDK 5 引入的，注解是代码里的特殊标记，这些标记可以在编译、类加载、运行时被读取，并执行相应的处理。开发人员可以在不改变原有逻辑的情况下，在源文件中嵌入一些补充的信息。

Annotation 不能运行，它只有成员变量，没有方法。Annotation 和 public、final 等修饰符一样，都是程序元素的一部分，Annotation 不能作为一个程序元素使用。

注解将一些本来重复性的工作，变成程序自动完成，简化和自动化该过程。比如用于生产 Java doc、自动代码生成等等。

#### 元注解

元注解可以理解为注解上的注解，有五个：

- @Retention
- @Target
- @Inherited
- @Documented
- @Repeatable

##### @Retention

定义该 Annotation 的生命周期。

- RetentionPoicy.SOURCE

  注解只保留在源文件中，当 Java 文件被编译成 class 文件的时候，注解被遗弃。常用于做一些检查性的操作，比如 @Override、@SuppressWarnings  。

- RetentionPoicy.CLASS

  注解被保留在 class 文件，但 JVM 加载 class 文件时被遗弃，这是默认的生命周期。常用于在编译时进行一些预处理，比如生成一些辅助代码，例如 ButterKnife。

- RetentionPoicy.RUNTIME

  注解不仅被保存到 class 文件中，JVM 加载 class 文件之后，仍然存在。常用于在运行时动态获取注解信息，大多会与反射一起使用。

##### @Target

定义了 Annotation 所修饰的对象结构。

- ElementType.CONSTRUCTOR	用于描述构造器
- ElementType.FIELD    用户描述属性
- ElementType.LOCAL_VARIABLE   用于描述局部变量
- ElementType.METHOD    用于描述方法
- ElementType.PACKAGE    用于描述包
- ElementType.TYPE    用于描述类、接口或枚举

如果未标注，则表示可修饰所有。

##### @Inherited

是否允许子类继承父类的注解，默认是 false。

##### @Documented

是否会保存到 Javadoc 文档中。

##### @Repeatable

JDK1.8新加的，表明当自定义注解的属性值有多个时，自定义注解可以多次使用。比如当成员变量是一个注解数组时：

```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Infos.class)
public @interface Info {
    String value();
}
```

```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Infos {
    Info[] value();
}
```

看到这，其实 Repeatable 修饰的 Infos 就可以看成是一个盛放 Info 注解的容器，这里需要注意的是，Info 和 Infos 修饰的 Target 一定要一致，下面就是取注解：

```java
@Info("Test 1")
@Info("Test 2")
public class AnnotationTest {

    public static void main(String[] args) throws NoSuchFieldException {
        if (AnnotationTest.class.isAnnotationPresent(Infos.class)) {
            Infos infos = AnnotationTest.class.getAnnotation(Infos.class);
            for (Info info : infos.value()) {
                System.out.println(info.value());
            }
        }
    }
}
```

#### 自定义注解

自定义注解可以根据有无成员变量分为标记型注解和元数据注解。定义了注解之后，还需要解析注解。解析注解也可以分为两种，它是根据 Retention 来划分的，如果是编译时注解，可以采用 APT 来解析，如果是运行时注解，则采用反射来获取。

##### 运行时注解

定义一个运行时注解，可以标注在类上、方法上、或者属性上。正如我们前面说的，如果不写，则可以修饰所有。

```java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
public @interface Info {
    String value();
}
```

通过反射处理该注解：

```java
@Info("Test Class")
public class AnnotationTest {

    @Info("Test field")
    private String id;

    @Info("Test method")
    public void test() {

    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {

        //解析类上的注解
        if (AnnotationTest.class.isAnnotationPresent(Info.class)) {
            Info infoClass = AnnotationTest.class.getAnnotation(Info.class);
            System.out.println(infoClass.value());
        }

        //解析方法上的注解
        Info infoMethod = AnnotationTest.class.getMethod("test").getAnnotation(Info.class);
        System.out.println(infoMethod.value());

        //解析成员变量上的注解
        Field field = AnnotationTest.class.getDeclaredField("id");
        field.setAccessible(true);
        Info infoField = field.getAnnotation(Info.class);
        System.out.println(infoField.value());

    }
}
```

这里涉及到的就是反射的知识了，在回顾一下好了。利用反射解析注解主要有以下三个方法：

1. boolean isAnnotationPresent(Class<? extends Annotation> annotation)

   判断是否使用了某个注解。

2. \<A extends Annotation> A getAnnotation(Class\<A> annotation)

   获得指定的注解元素。

3. Annotation[] getAnnotations()

   返回对应元素上的所有注解。

##### 编译时注解

编译时注解的解析，是通过 APT 来实现的。我们可以通过继承 AbstractProcress 来自定义注解处理逻辑，但是还得需要向编译器注册注解处理器，这是一件很麻烦的事，需要在 META-INFO 目录下手动注册，一般是通过依赖 Google 的 AutoService 库来解决。

APT 在前面已经写过了，可以参考：[APT](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/APT.md)

运行原理：

Java 源代码的编译流程分为三个步骤：

1. 将源文件解析成抽象语法树
2. 调用已注册的注解处理器
3. 生成字节码

如果第二步调用注解处理器过程中生成了新的源文件，那么编译器将重复第一二步骤，解析并处理新生成的源文件。

所以可以这样理解，我们写的自定义注解处理器是给编译器写的，让它按照我们的逻辑来处理注解，所以也得向编译器注册注解处理器。

#### 常用注解

##### Android 常用注解

在 android.support.annotation 包中提供了很多好用的注解，比如：

- 资源类型注解

  用来标注元素必须为指定的资源类型，例如 @AnimRes、@ColorRes、@LayoutRes 等等。

- 空值注解

  @Nullable 可空类型，@NonNull 不可空类型。

- 值约束注解

  即约束元素取值范围，如 @IntRange、@FloatRange。

- 权限注解

  检查某操作是否有必要的权限，比如@RequiresPermission。

- 线程注解

  可以标注方法、类、接口等只能在指定的线程被调用，例如 @MainThread、@UIThread、@WorkerThread 等。

##### Java 常用注解

- @Override
- @Deprecated
- @SuppressWarnings

#### 参考

[浅谈Android下的注解](https://juejin.im/post/5b292b736fb9a00e8c4435f7)

[注解处理器](https://time.geekbang.org/column/article/40189)

[Java 注解及其在 Android 中的应用](https://juejin.im/post/5b824b8751882542f105447d)