注解

学习目标

  • 注解概述
  • 如何使用注解
  • 自定义注解
  • 注解处理器
  • 实例

什么是注解

  • 注解(也被称为元数据)是指程序功能外,在代码中添加的额外信息,这些信息可以用来修饰、标识功能代码,但不影响代码运行。
  • 注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。

为什么使用注解

  • Annotation 一般可以取代复杂的配置文件,用于告之容器管理者某个类、方法的行为。
  • Annotation 是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。
  • 如果说反射是很多技术实现(动态代理、依赖注入等)的基础,那么注解就是用使这些技术平民化的基础
  • 注解是以‘@注解名’在代码中存在的。

根据注解参数的个数,我们可以将注解分为:

  • 标记注解:没有变量,只有名称标识。例如:@annotation。
  • 单值注解:在标记注解的基础上提供一段数据。如:@annotation(“data”)。
  • 完整注解:可以包括多个数据成员,每个数据成员由名称和值构成。如:@annotation(val1=“data1”, val2=“data2”)。

Java常用注解——@Override

在java.lang 中

1
2
3
@Target(value=METHOD) 
@Retention(value=SOURCE)
public @interface Override

表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。

1
2
3
4
5
6
7
8
9
10
11
12
class A {
private String id;

A(String id) {
this.id = id;
}

@Override
public String toString() {
return id;
}
}

Java常用注解——@Deprecated

在java.lang 包中

1
2
3
@Documented 
@Retention(value=RUNTIME)
public @interface Deprecated

@Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。

1
2
3
4
5
6
7
8
@Deprecated
public abstract class Person {
}

public class Person {
@Deprecated
public String name;
}

Java常用注解——@SuppressWarnings

在java.lang中

1
2
3
@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@Retention(value=SOURCE)
public @interface SuppressWarnings

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。注意,在给定元素中取消显示的警告集是所有包含元素中取消显示的警告的超集。

1
2
3
4
5
6
7
8
9
10
public class Person {
@SuppressWarnings(value = "unused")
private String name;

public void speak(String message) {
@SuppressWarnings({ "unchecked", "unused" })
List list = new ArrayList();
System.out.println("Speak: " + message);
}
}

元注解

元注解是java定义的用于创建注解的工具,它们本身也是注解。

@Target

@Taget 注解表明了自定义注解的作用域。可能的作用域被定义在一个枚举类型中:ElementType。ElementType 中的常量值如下:

  • ANNOTATION_TYPE :作用在注解类型上的注解。
  • CONSTRUCTOR :作用在构造方法上的注解。
  • FIELD :作用在属性上的注解。
  • LOCAL_VARIABLE :作用在本地变量上的注解。
  • METHOD :作用在方法上的注解。
  • PACKAGE :作用在包上的注解。
  • PARAMETER :作用在参数上的注解。
  • Type :作用在类、接口或枚举上的注解。

@Retention

@Retention 用于声明注解信息的保留策略,可选的级别被存放在枚举 RetentionPolicy 中,该枚举中的常量值如下:

  • RetentionPolicy.SOURCE :注解信息仅保留在源文件中,编译时将丢弃注解信息。
  • RetentionPolicy.CLASS :注解信息将被编译进Class文件中,但这些注解信息在运行时将丢弃。
  • RetentionPolicy.RUNTIME :注解信息将被保留到运行时,你可以通过反射来读取这些注解信息。

@Documented

Documented 注解表明制作 javadoc 时,是否将注解信息加入文档。如果注解在声明时使用了 @Documented,则在制作 javadoc 时注解信息会加入 javadoc。

@Inherited

Inherited 注解表明注解是否会被子类继承,缺省情况是不继承的。当注解在声明时,使用了 @Inherited 注解,则该注解会被使用了该注解的类的子类所继承。

自定义注解

  • 使用 @interface 自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
  • 注解的定义跟接口很相似,而且,注解类型编译后也会产生一个Class文件,这与接口和类无异。
1
2
3
4
5
6
7
8
9
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}

为自定义注解添加变量

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
public int type() default -1;
public String name();
}

使用自定义注解

1
2
3
4
5
@Entity(name = "Person")
public class Person {
@Test
public void speak(String message) {}
}

注解参数说明

参数类型必须使用指定参数类型

  • 所有基本类型,包括(int,float,boolean)
  • String
  • Class
  • enum
  • Annotation
  • 以及以上类型的数组

多变量使用枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public @interface MyAnnotation {
String value1() default "abc";

MyEnum value2() default MyEnum.Sunny;
}

enum MyEnum {
Sunny, Rainy
}

public class AnnotationTest2 {
@MyAnnotation(value1 = "a", value2 = MyEnum.Sunny)
public void execute() {
System.out.println("method");
}
}

数组变量

1
2
3
public @interface MyAnnotation {
String[] value1() default "abc";
}

使用自定义注解:

1
2
3
4
5
6
public class AnnotationTest2 {
@MyAnnotation(value1 = { "a", "b" })
public void execute() {
System.out.println("method");
}
}

嵌套

1
2
3
4
5
6
7
8
9
10
11
12
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
public String value() default "id";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
public String name() default "";
public ID id() default @ID("id");
}

注解参数的赋值要求

  • 编译器要求注解的参数不能是不确定值,即要么在定义注解的时候就进行赋值,要么在使用的时候进行赋值。
  • 如果定义一个参数而未进行赋值,则编译器会抛出一个错误:The annotation must define the attribute value。

注解参数的快捷方式

1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
public String value();
public String description() default "";
}

public class Person {
@ID("personID")
private Integer id;
}

注解处理器

  • 如果没有读取分析注解的工具,那注解就不会比注释更有用。
  • JDK5扩展了反射机制的API,可以帮助程序员有效的创建这类工具,而且,它还提供了一个外部工具apt,用于帮助程序员分析处理注解。
  • 要想使用反射去读取注解,必须将Retention的值选为Runtime。

Apt工具

  • Apt是由sun开发的一款用于处理注解的工具。
  • apt与javac一样用于处理源代码级别的命令。
  • 默认情况下apt会在代码未编译前对代码进行分析处理。
  • Apt首先通过注解处理器分析处理用户编写的源文件,如果该轮处理的过程中产生了新文件,则apt会对产生的文件进行新一轮的处理,直至不再产生新文件为止,然后将这些文件一同编译,因此一般情况下apt命令包含了javac的功能。
  • apt是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API
  • Mirror API(com.sun.mirror.*)描述的是程序在编译时刻的静态结构。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑。具体的处理工作交给apt工具来完成。
  • 编写注解处理器的核心是 AnnotationProcessorFactoryAnnotationProcessor 两个接口。后者表示的是注解处理器,而前者则是为某些注解类型创建注解处理器的工厂。

编写Apt应用一般包括以下四个步骤:

  • 编写需要进行注解处理的类(一般地,这些类应该带有需要处理的注解)。
  • 实现至少一个 AnnotationProcessorFactory
  • 实现 AnnotationProcessor
  • 使用APT命令行,执行注解处理。

描述代码分工安排的注解。通过该注解可以在源代码中记录每个类或接口的分工和进度情况。

1
2
3
4
5
6
7
8
9
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Assignment {
String assignee();

int effort();

double finished() default 0;
}

AnnotationProcessorFactory 接口有三个方法:

  • getProcessorFor 是根据注解的类型来返回特定的注解处理器;
  • supportedAnnotationTypes 是返回该工厂生成的注解处理器所能支持的注解类型;
  • supportedOptions 用来表示所支持的附加选项。在运行apt命令行工具的时候,可以通过-A来传递额外的参数给注解处理器,如-A verbose=true。当工厂通过 supportedOptions 方法声明了所能识别的附加选项之后,注解处理器就可以在运行时刻通过 AnnotationProcessorEnvironmentgetOptions 方法获取到选项的实际值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AssignmentApf  implements AnnotationProcessorFactory { 
public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,? AnnotationProcessorEnvironment env) {
if (atds.isEmpty()) {
return AnnotationProcessors.NO_OP;
}
return new AssignmentAp(env); //返回注解处理器
}
public Collection<String> supportedAnnotationTypes() {
return Collections.unmodifiableList(Arrays.asList("annotation.Assignment"));
}
public Collection<String> supportedOptions() {
return Collections.emptySet();
}
}

注解处理器本身的基本实现

  • 注解处理器的处理逻辑都在 process 方法中完成。
  • 通过一个声明(Declaration)的 getAnnotationMirrors 方法就可以获取到该声明上所添加的注解的实际值。
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
public class AssignmentAp implements AnnotationProcessor {
private AnnotationProcessorEnvironment env;
private AnnotationTypeDeclaration assignmentDeclaration;

public AssignmentAp(AnnotationProcessorEnvironment env) {
this.env = env;
assignmentDeclaration = (AnnotationTypeDeclaration) env
.getTypeDeclaration("annotation.Assignment");
}

public void process() {
Collection<Declaration> declarations = env
.getDeclarationsAnnotatedWith(assignmentDeclaration);
for (Declaration declaration : declarations) {
processAssignmentAnnotations(declaration);
}
}

private void processAssignmentAnnotations(Declaration declaration) {
Collection<AnnotationMirror> annotations = declaration
.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if (mirror.getAnnotationType().getDeclaration()
.equals(assignmentDeclaration)) {
Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror
.getElementValues();
String assignee = (String) getAnnotationValue(values,
"assignee"); // 获取注解的值
}
}
}
}
  • 在创建好注解处理器之后,就可以通过apt命令行工具来对源代码中的注解进行处理。 命令的运行格式是apt -classpath bin -factory annotation.apt.AssignmentApfsrc/annotation/work/.java,即通过-factory*来指定注解处理器工厂类的名称。
  • 实际上,apt工具在完成处理之后,会自动调用javac来编译处理完成后的源代码。

热评文章