You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
7.3 KiB
246 lines
7.3 KiB
---
|
|
注解
|
|
---
|
|
|
|
#### 目录
|
|
|
|
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) |