注解(Annotation)就是 Java 提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来获取注解里面的元数据。
Annotation 是 JDK 5.0 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。Annotation 是一种应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中的特殊修饰符。这些信息被存储在 Annotation 的 “name=value” 结构对中。
在注解出现之前,程序的元数据只是通过 java 注释和 javadoc,但是注解提供的功能要远远超过这些。注解不仅包含了元数据,它还可以作用于程序运行过程中、注解解释器可以通过注解决定程序的执行顺序。
比如,下面这段代码:
@Override
public String toString() {
return "This is a string representation of the current object.";
}
元数据从 metadata 一词译来,就是“关于数据的数据”的意思。
元数据的功能作用有很多,比如:你可能用过 Javadoc 的注释自动生成文档。这就是元数据功能的一种。总的来说,元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。如果要对于元数据的作用进行分类,目前还没有明确的定义,不过可以根据它所起的作用,大致可分为三类:
使用 Annotation 之前(甚至在使用之后),XML 被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现 XML 的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在 Google 中搜索 XML vs. annotations,会看到许多关于这个问题的辩论。最有趣的是 XML 配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。
假如你想为应用设置很多的常量或参数,这种情况下,XML 是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用 Annotation 会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。
另一个很重要的因素是 Annotation 定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记 interfaces,注释,transient 关键字等等。每个程序员按照自己的方式定义元数据,而不像 Annotation 这种标准的方式。
目前,许多框架将 XML 和 Annotation 两种方式结合使用,平衡两者之间的利弊。
Annotation 使用了在 Java5 所带来的新语法,它的行为十分类似 public、final 这样的修饰符。每个 Annotation 具有一个名字和零个或多个成员。每个 Annotation 的成员具有被称为 name=value 对的名字和值(就像 JavaBean 一样),name=value 装载了 Annotation 的信息。
Annotation 类型定义了 Annotation 的名字、类型、成员默认值。一个 Annotation 类型可以说是一个特殊的 Java 接口,它的成员变量是受限制的,而声明 Annotation 类型时需要使用新语法。当我们通过 Java 反射 API 访问 Annotation 时,返回值将是一个实现了该 Annotation 类型接口的对象,通过访问这个对象能方便的访问到其 Annotation 成员。
按照注解的参数个数划分,注解可以分为以下三种:
按照运行机制划分,注解可以分为以下三种:
按照使用方法和用途划分,注解可以分为以下三种:
元注解是给注解进行注解,可以理解为注解的注解就是元注解。
在 java.lang 包下,JDK 提供了 5 个基本注解:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface。
@Override 用于标注重写了父类的方法。
当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法,这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。对于子类中被 @Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。
@Deprecated 用于表示某个程序元素(类、方法等)已过时。
当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。Java 在 javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。如果使用了被 @Deprecated 修饰的类或方法等,编译器会发出警告。
@SuppressWarnings 用于抑制编译器的警告。
这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。指示被@SuppressWarnings 修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法)取消显示指定的编译器警告。例如,常见的 @SuppressWarnings("unchecked")。
@SuppressWarnings 注解的常见参数值主要有以下几种:
@SafeVarargs 是 JDK 7 专门为抑制堆污染警告提供的。
@FunctionalInterface 是 Java8 中新增的函数式接口。Java8 规定:如果接口中只有一个抽象方法(可以包含多个 default 方法或多个 static 方法),该接口称为函数式接口。
注意: value 特权:如果使用注解时只需要为 value 成员变量指定值,则使用注解时可以直接在该注解的括号中指定 value 值,而无需使用 name=value 的形式(如@SuppressWarnings("unchecked"))。坚持使用 @Override 注解:如果在每个方法中使用 @Override 注解来声明要覆盖父类声明,编译器就可以替你防止大量的错误。
元注解(meta-annotation)是指注解的注解。元注解是 Java 定义的用于创建注解的工具,它们本身也是注解。在 java.lang.annotation 包下,JDK 提供了 5 个标准的元注解类型:@Retention、@Target、@Inherited、@Documented、@Repeatable。
@Retention:注解的保留策略。该注解指明了被它所注解的注解被保留的时间长短。
@Retention 包含一个名为 value 的成员变量,该 value 成员变量是 RetentionPolicy,RetentionPolicy 是枚举类型,值有如下几个:
@Target:注解的作用目标。该注解指定注解用于修饰哪些程序元素。
@Target 也包含一个名为 value 的成员变量,该 value 成员变量类型为 ElementType[],ElementType 也为枚举类型,值有如下几个:
@Inherited:指定注解具有继承性。但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Documented:注解将被包含在 Javadoc 中。该注解的作用是在用 Javadoc 命令生成 API 文档时能够将注解中的元素包含到 Javadoc 中去。
@Repeatable: 表示可重复注解。@Repeatable 是 Java 8 才加进来的,所以算是一个新的特性。
在实际应用中,可能会出现需要对同一个声明式或者类型加上相同的注解(包含不同的属性值)的情况。例如系统中除了管理员之外,还添加了超级管理员这一权限,对于某些只能由这两种角色调用的特定方法,可以使用可重复注解。
使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、Enum)。可以通过 default 来声明参数的默认值。
定义注解格式:
public @interface 注解名 {
定义体
}
自定义注解就需要用到上面所介绍到的几种元注解,可以看出元注解就是用来注解其它注解。自定义注解和接口类似,只能定义方法。注解参数的可支持数据类型:
定义新注解使用 @interface 关键字,其定义过程与定义接口非常类似,需要注意的是:Annotation 的成员变量在 Annotation 定义中是以无参的方法形式来声明的,其方法名和返回值类型定义了该成员变量的名字和类型,而且还可以使用 default 关键字为这个成员变量设定默认值。
自定义注解的示例如下:
package annotation.custom;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Tag {
String name() default "undefined";
String description();
}
Java 使用 Annotation 接口来代表程序元素前面的注解,该接口是所有 Annotation 类型的父接口。自定义的注解继承了 Annotation 这个接口,因此自定义注解中包含了 Annotation 接口中所有的方法:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
Java 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时 Annotation 信息的能力。当一个 Annotation 类型被定义为运行时的 Annotation 后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method、Constructor、Field、Parameter、Package)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的如下方法来访问 Annotation 信息:
方法 | 描述 |
---|---|
boolean isAnnotationPresent(Class<? extends Annotation> annotationType) | 判断指定对象是否应用了某个注解,此方法主要用于方便地访问标记注释 |
Annotation[] getAnnotations() | 返回作用于指定对象的所有注解,不存在则返回长度为 0 的数组 |
Annotation[] getDeclaredAnnotations() | 返回直接作用于指定对象的所有注解,不存在则返回长度为 0 的数组(此方法忽略继承的注解) |
<T extends Annotation> T getAnnotation(Class<T> annotationClass) | 返回指定类型的注解,不存在则返回 null |
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) | 返回直接作用于指定对象的指定类型的注解,不存在则返回 null(此方法忽略继承的注解) |
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) | 返回指定类型的注解,不存在则返回长度为 0 的数组,此方法检测其参数是否为可重复的注解类型 |
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) | 返回直接作用于指定对象的指定类型的注解,不存在则返回长度为 0 的数组,此方法检测其参数是否为可重复的注解类型(此方法忽略继承的注解) |
只有当定义 Annotation 时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰,JVM 才会在装载 class 文件时提取保存在 class 文件中的 Annotation,该 Annotation 才会在运行时可见。否则 class 文件的注解信息在执行过程中将不可用,从而也就不能从中得到任何和注解有关的数据。
注解处理器是(Annotation Processor)是 javac 内置的一个用于在编译时扫描、编译和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。
在 Java 5 首次引入注解的时候,注解处理器的 API 还没有成熟,也没有标准化。处理注解需要一个名为 apt(Annotation Processor Tool,注解处理器工具)的独立的工具,以及包含在 com.sum.mirror 包中的 Mirror API。apt 需要使用 Mirror API 来自定义处理器。
从 Java 6 开始,注解处理器通过 JSR 269 已经标准化并被纳入到标准库中,APT 工具也被无缝集成到 Java 编译工具 javac 里面。Java 6 提供了一个已经实现通用功能的抽象类 javax.annotation.processing.AbstractProcessor,同时还提供了 javax.lang.model包。
由于注解处理器可以在程序编译阶段工作,所以开发者可以在编译期间通过注解处理器进行开发者需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成 .java 源文件(让机器帮开发者写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。
AbstractProcessor 是一个抽象类,该类实现了 Processor 接口。抽象类 AbstractProcessor 以及接口 Processor 都是位于包 javax.annotation.processing 中。该包中定义的所有类、接口都是与实现注解处理器相关的。如下表所示:
类/接口 | 描述 |
---|---|
Completion | 某一注释的建议 completion |
Filer | 此接口支持通过注释处理器创建新文件 |
Messager | Messager 提供注释处理器用来报告错误消息、警告和其他通知的方式 |
ProcessingEnvironment | 注释处理工具框架将提供一个具有实现此接口的对象的注释 processor,因此 processor 可以使用该框架提供的设施来编写新文件、报告错误消息并查找其他实用工具 |
Processor | 注释 Processor 的接口 |
RoundEnvironment | 注释处理工具框架将提供一个注释处理器和一个实现此接口的对象,这样处理器可以查询有关注释处理的 round 的信息 |
AbstractProcessor | 旨在用作最具体注释 processor 的便捷超类的抽象注释 processor |
Completions | 用来组合 Completion 对象的实用工具类 |
FilerException | 指示一个检测到试图打开某一文件的 Filer,该文件违反 Filer 提供的保证 |
SupportedAnnotationTypes | 用来指示注释处理器支持哪些注释类型的注释 |
实现一个自定义注解处理器需要有两个步骤,第一是实现 Processor 接口处理注解,第二是注册注解处理器。
通过实现 Processor 接口可以自定义注解处理器,可以采用更简单的方法通过继承 AbstractProcessor 类实现自定义注解处理器,并实现抽象方法 process 处理想要的功能。
AbstractProcessor 具有如下方法:
方法 | 描述 |
---|---|
void init(ProcessingEnvironment processingEnv) | 用处理环境初始化 processor,方法是将 processingEnv 字段设置为 processingEnv 参数的值 |
boolean isInitialized() | 如果此对象已被初始化,则返回 true,否则返回 false |
Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) | 返回一个空的 completion 迭代 |
Set<String> getSupportedAnnotationTypes() | 如果 processor 类是使用 SupportedAnnotationTypes 注释的,则返回一个不可修改的集合,该集合具有与注释相同的字符串集 |
Set<String> getSupportedOptions() | 如果 processor 类是使用 SupportedOptions 注释的,则返回一个不可修改的集合,该集合具有与注释相同的字符串集 |
SourceVersion getSupportedSourceVersion() | 如果 processor 类是使用 SupportedSourceVersion 注释的,则返回注释中的源版本 |
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) | 处理先前 round 产生的类型元素上的注释类型集,并返回这些注释是否由此 Processor 声明 |
注意:在 Java 6 及以上版本中,getSupportedAnnotationTypes() 方法和 getSupportedSourceVersion() 方法分别可以通过 @SupportedAnnotationTypes 注解和 @SupportedSourceVersion 注解来替换。
自定义注解处理器都需要继承于 AbstractProcessor,如下所示:
/**
* 自定义注解处理器
*/
public class CustomProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(Tag.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<? extends Element> tagElements = roundEnvironment.getElementsAnnotatedWith(Tag.class);
for (Element element : tagElements) {
// 1.获取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
printMessage(String.format("package = %s", pkName));
// 2.获取包装类类型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
printMessage(String.format("enclosindClass = %s", enclosingName));
// 3.获取注解的成员变量名
String tagFiledName = element.getSimpleName().toString();
// 4.获取注解的成员变量类型
String tagFiledClassType = element.asType().toString();
// 5.获取注解元数据
Tag tag = element.getAnnotation(Tag.class);
String name = tag.name();
printMessage(String.format("%s %s = %s", tagFiledClassType, tagFiledName, name));
// 6.生成文件
createFile(enclosingElement, tagFiledClassType, tagFiledName, name);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement, String tagFiledClassType, String tagFiledName, String name) {
String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFileObject javaFileObject = mFiler.createSourceFile(pkName + ".Tag");
Writer writer = javaFileObject.openWriter();
writer.write(generateCode(pkName, tagFiledClassType, tagFiledName, name));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void printMessage(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private String generateCode(String pkName, String tagFiledClassType, String tagFiledName, String name) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("//Auto generated by apt,do not modify!!\n\n");
builder.append("public class Tag { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %s", tagFiledClassType, tagFiledName, name);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
}
在 init() 中可以获得如下引用:
在注解处理过程中,扫描所有的 Java 源文件。源代码的每一个部分都是一个特定类型的 Element。换句话说:Element 代表程序的元素,例如包、类或者方法。每个 Element 代表一个静态的、语言级别的构件。
Element 代表的是源代码,它的子类有这些:
注册注解处理器有两种方法:
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多使用场景:
注解有许多用处,主要如下:
使用 Annotation 修饰了类、方法、成员变量等之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
source:https://henleylee.github.io/posts/2018/52b68731.html
喜欢,在看