注解
一种用于解释java代码的标签,用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
注解种类:
- 元注解
- java自带注解
- 自定义注解
自定义注解:
语法格式
public @interface MyAnnotation {
}
元注解:用于修饰自定义注解
- Retention:说明自定义注解的声明周期
RetentionPolicy.RUNTIME // 注解保留到程序运行时
RetentionPolicy.CLASS // 注解保留到编译
RetentionPolicy.SOURCE // 注解保留到源码阶段(编译后.class文件不会有这个注解)
-
Documented:文档注解,将自定义注解的元素包含到javadoc文档中(不常用)
-
Target:描述注解的使用范围
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
- Inherited:使自定义注解具有继承性
@Inherited
public @interface InheritAnnotation {
}
@InheritAnnotation
public class Person {
}
class Man extends Person{
}
Man类具有InheritAnnotation注解属性;
- Repeatable:表示这个注解可以重复使用
public @interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
public @interface Person {
String value() default "";
}
@Person(value = "222")
@Person(value = "111")
class RepeatableAnnotation{
}
注解的属性:
注解只有成员变量,没有方法。需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型,默认值需要用 default 关键值指定
public @interface Person {
String name();
String position() default "普工";
int age();
boolean isMale();
}
注解属性的提取(运行时注解提取):
使用反射提取类的注解信息;
定义一个Employee注解,用来存放员工的基本信息
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Employee {
String name();
String position() default "普工";
int age();
boolean isMale();
}
两个员工类,tony,amy,分别指定他们注解的值
@Employee(name = "tony甲",position = "经理",age = 26,isMale = true)
public class Tony {
}
@Employee(name = "amy红",position = "HR",age = 30,isMale = false)
class Amy{
}
通过反射获取他们注解所带的值
public static void main(String[] args) {
Tony tony = new Tony();
Amy amy = new Amy();
Class tonyClass = tony.getClass();
Employee tonyClassAnnotation = (Employee) tonyClass.getAnnotation(Employee.class);
System.out.println(tonyClassAnnotation);
Class amyClass = amy.getClass();
Annotation[] amyClassAnnotations = amyClass.getAnnotations();
Employee amyClassAnnotation = (Employee) amyClassAnnotations[0];
System.out.println(amyClassAnnotation);
}

注解的作用:
- 提供信息给编译器:编译器可以利用注解来探测错误和警告信息
- 编译阶段的处理:软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取值得注意的是,注解不是代码本身的一部分。
APT (编译期注解提取)
APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。
下面介绍APT技术的几个重要概念
- AbstractProcess 注解处理器:一个抽象类,用于自定义注解处理器,需要继承这个类,并实现它的四个方法;
- AutoService 处理器注册服务:谷歌提供的一个自动注册注解处理器的服务,省去写MATA文件的步骤
- JavaPoet 代码生成工具:一个第三方的API,通过这个API可以生成模板类型的java代码
AbstractProcess 和 AutoService 实现编译期解析注解
- 我们需要创建两个java Module annotation和processors

注意这两个都是java Module,annotation用来存放我们的自定义注解,processor用来存放我们的自定义注解处理器;app是我们的工程项目,他们的依赖关系是app依赖annotation和processors,processors依赖annotation;
- 创建自定义注解,在项目中引用注解



- 创建自定义注解处理器
首先引入谷歌的自动注册服务,AutoService
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.google.auto:auto-common:0.10'

自定义注解处理器
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
/**
* 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
* 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
* @param annotations 请求处理的注解类型
* @param roundEnv 有关当前和以前的信息环境
* @return 如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
* 如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("-----process():-------");
// 通过我们指定的注解返回与这个注解绑定的element集合
Set<? extends Element> annotationElements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
// 遍历当前element集合
for (Element element : annotationElements){
// 通过element的种类将他转成element的实现类
if (element.getKind() == ElementKind.CLASS){
TypeElement typeElement = (TypeElement) element;
// 通过element的具体的实现类获取注解的值
System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());
}
}
return false;
}
// 规定这个注解处理器处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(MyAnnotation.class.getCanonicalName());
return set;
}
//指定java版本
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
下面重点介绍Element接口
Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的 Element。它可以代表包、类、接口、方法、字段等多种元素种类,具体看getKind()方法中所指代的种类,每个Element 代表一个静态的、语言级别的构件。
/**
* 返回此元素的种类:包、类、接口、方法、字段...,如下枚举值
* PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE,
* ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE,
* EXCEPTION_PARAMETER,METHOD, CONSTRUCTOR, STATIC_INIT,
* INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE;
*/
ElementKind getKind();
getKind()用于获取这个Element的具体种类,这些种类都存放在枚举类ElementKind中
Element接口有五个默认实现类:
- PackageElement: 表示一个包程序元素
- TypeElement: 表示一个类或接口程序元素
- VariableElement: 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
- ExecutableElement: 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
- TypeParameterElement: 表示一般类、接口、方法或构造方法元素的泛型参数
在process()中,我们通过当前环境信息RoundEnvironment和我们指定要解析的注解拿到和这个注解相关的Element集合,再遍历element集合通过它的Kind将它转成具体的Element实现类,然后通过这个实现类去获取注解;
网友评论