2016-09-21 | learn

Java Annotation

Annotation 是什么

Annotation (注解) 就是我们平时写代码里经常遇到的 @Override这种的东西。看到@字符开头就知道后面跟的是一个注解啦。 在各种开源库里也经常看到注解的身影,为开发提供了大大的便利。

Java Annotation 主要有以下几种用途:

  • 为编译器提供信息, 编译器通过注解提供的信息检查代码。
  • 编译时或部署时,工具应用可以通过注解信息生成代码或 XML 文件等。
  • 运行时注解,在运行时检查注解并进行特殊处理。

注解使用的方式很多,可以放置在类声明,字段,方法或者其他编程元素上。随着 Java 8 的发布,注解还可以用作类型。被称为 type annotation,Learn more

1
2
3
4
5
6
7
8
9
10
11
12
// Type Annotation,

// 创建类实例表达式
new @Interned MyObject();
// 类型转换
myString = (@NonNull String) str;
// 实现接口
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
// 抛出异常声明
void monitorTemperature() throws
@Critical TemperatureException { ... }

如何创建注解

创建注解和创建一个接口很像,只不过在interface前多了一个@。注解使用方式很多,对应的创建方式也各不相同。

最简单的注解

1
2
3
public @interface NanoAnnotation{
// 啥都没干
}

注解相关的注解

  • @Retention:声明被标记的注解如何储存的

    1. RetentionPolicy.SOURCE: 只在源码保留,编译器会忽略
    2. RetentionPolicy.CLASS: 编译器会保留它,JVM 运行时会忽略
    3. RetentionPolicy.RUNTIME: JVM在运行时会使用
  • @Documented: 注解默认是不会包含进 Javadoc 的,使用@Documented可以强制 Javadoc tool 记录被标记的注解。Learn more.

  • @Target: 限制注解的使用范围

    1. ElementType.ANNOTATION_TYPE: 应用于注解。
    2. ElementType.CONSTRUCTOR: 应用于构造函数。
    3. ElementType.FIELD:应用于字段或属性(包括 Enum 常量)
    4. ElementType.LOCAL_VARIABLE:应用于局部变量。
    5. ElementType.METHOD: 应用于方法级注解。
    6. ElementType.PACKAGE: 应用于 package 。
    7. ElementType.PARAMETER: 应用于方法的参数。
    8. ElementType.TYPE: 应用于 class,interface 或者 enum。
    9. ElementType.TYPE_USE:Use of a type (@since 1.8) 。
    10. ElementType.TYPE_PARAMETER:Type parameter declaration (@since 1.8) 。
  • @Inherited: 表示被标记的注解在使用时,其注解的类的子类也继承这个注解。这个特性默认状态是关闭的。

  • @Repeatable: Java 8 引入,表示被标记的注解在同一位置可以重复使用多次。Learn more.

注解的解析

编译时注解的解析

ButterKnife ButterKnifeProcessor.java

解析编译时注解需要自定义 AbstractProcessor,其中最主要的就是重写process方法,在这里可以根据注解生成新的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override 
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingSet binding = entry.getValue();

JavaFile javaFile = binding.brewJava(sdk);
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
}
}

return true;
}

重写getSupportedAnnotationTypes可以返回所有支持的注解。

1
2
3
4
5
6
7
8
@Override 
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}

重写getSupportedSourceVersion可以返回 Processor 支持的版本信息。

1
2
3
4
@Override 
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

注意:想要运行 Processor 需要配合 apt 工具

运行时注解的解析

解析运行时注解需要使用反射。在运行时可以获取到注解相关信息,

Method Annotation

Field Annotation

Retrofit ServiceMethod.java

下面就是 Retrofit 在解析 @Post@Get这些注解时的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...

public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
// 在此获取 Annotation
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}

public ServiceMethod build() {
...
for (Annotation annotation : methodAnnotations) {
// 解析所有运行时 Annotation
parseMethodAnnotation(annotation);
}
...
}

...

// 跳转到各个 Annotation 对应的详细解析方法
private void parseMethodAnnotation(Annotation annotation) {
...
else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
}
...
else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError("@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError("Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError("Only one encoding annotation is allowed.");
}
isFormEncoded = true;
}
}

...

参考资料