一、注解的定义
1.1 通用的注解声明方式
public @interface 注解名 {
//定义成员
}
- 通过
@
interface 表示这是一个注解接口; - 注解中的成员变量在注解声明中以无参数方法的形式来声明,其方法名定义了该成员的名字,返回值定义了该成员的类型,注解中成员变量的类型是受限制的,只能是基本类型及其包装类、String类型、Class类型、枚举类型、注解类型、以及这些类型的数组。
1.2 使用元注解定义注解
在定义注解时,必须使用元注解 @Retention、@Target、@Inherited 和 @Documented 说明注解的作用域、使用范围、是否可继承和是否输出到 API 文档中。
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target(ElementType.TYPE)
@Documented
public @interface MyAnnotation {
String name() default "default";
}
- "@Retention"表示保留期,它的值定义了该注解的生命周期。RetentionPolicy.RUNTIME 表示在运行时可以获取注解信息;
- “@Target” 表示适用范围,可以指定作用在注解上的目标元素类型,如类、方法、成员变量等。多个目标类型可以用逗号隔开,如果省略该元素,表示定义的注解可以用在所有的程序元素上。
- “@Inherited” 表示注解可以被继承,默认情况下,注解不会被继承。如果注解需要被子类继承,可以在定义注解时使用 @Inherited 元注解。
- “@Documented” 表示注解中的元素可以被 javadoc 工具读取并生成文档。
1. @Retention
@Retention 注解指定了被修饰的注解保留多长时间。
@Retention 属性取值如下:
- RetentionPolicy.SOURCE:注解只存在于编译器处理期间,不会在编译输出的 class 文件中出现,适用于限制程序员编写代码时使用的注解。
- RetentionPolicy.CLASS:注解会被编译器记录在 class 文件中,但是在运行时 JVM 不会保留,适用于需反射获取注解信息的情况。
- RetentionPolicy.RUNTIME:注解会被编译器记录在 class 文件中,同时在 JVM 运行时也会保留,可以通过反射动态获取注解信息。
2. @Target
@Target 注解指定了注解可以被用来修饰哪些程序元素,例如类、方法、域等。
@Target 的取值类型有以下常量:
- ElementType.TYPE:用于标注类、接口、枚举。
- ElementType.CONSTRUCTOR:用于标注构造方法。
- ElementType.FIELD:用于标注成员变量。
- ElementType.METHOD:用于标注方法。
- ElementType.PARAMETER:用于标注参数。
- ElementType.LOCAL_VARIABLE:用于标注局部变量。
- ElementType.ANNOTATION_TYPE:用于标注注解。
- ElementType.PACKAGE:用于标注包。
3. @Inherited
@Inherited 注解表示标注了此注解的类的子类也会被标注此注解。
4. @Documented
@Documented 注解也被称为文档化注解,加上这个注解后,注解的信息就可以被 javadoc 所解析。
1.3 注解中的成员
注解中的成员可以使用 default 关键字指定默认值。
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodInfo {
String name() default "TestMethod";
String data() default "2020-05-10";
int id() default 0;
}
name、data、id 都是注解中的成员变量,在注解使用时都可以使用这些成员变量来对注解进行赋值。
二、使用注解
2.1 注解的解析
可以通过反射机制获取到指定类或方法上的注解信息。
public class RunMyAnnotation {
@MethodInfo(id=1,name = "Main",data="2022-08-22",value="")
public void test1(){
System.out.println("test1");
}
public static void main(String[] args) {
try {
Method method = RunMyAnnotation.class.getMethod("test1");
MethodInfo annotation = method.getAnnotation(MethodInfo.class);
System.out.println("id: " + annotation.id());
System.out.println("name: " + annotation.name());
System.out.println("data: " + annotation.data());
System.out.println("value: " + annotation.value());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
在上面的代码中,使用了反射机制获取到指定类中指定方法的注解信息。
2.2 给注解赋值
注解中成员的赋值,可以使用“成员名=值”的方式进行赋值,如果只有一个成员需要赋值,并且成员名称为"value",那么在赋值时,可以省略成员名称和“=”。
- 指定变量名进行赋值:
@MethodInfo(name="sayHello")
public void say(){
System.out.println("Hello Annotation");
}
- 只有一个默认值’value’进行赋值:
@MethodInfo("HelloWord!")
public void say(){
System.out.println("Hello Annotation");
}
2.3 编译时注解处理器
Java 提供了 apt 工具来处理注解,Apt 是 Annotation Processing Tool 的缩写。它是负责在编译期扫描和处理类的注解的程序,也就是提供注释处理器服务的程序。
apt 在 Eclipse 和 IDEA 中的默认设置是关闭的,需要进行配置。
2.4 自定义注解处理器
注解处理器的工作就是扫描源代码中的注解,并解析和使用注解。
自定义注解处理器一般包括两部分:
- 注解声明类型;
- 注解处理类。
以 @BindView 注解为例子:
2.4.1 定义注解 @BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
这个注解用于设置控件的 ID,它只能用于成员变量,作用域是运行时。
2.4.2 自定义注解处理器
自定义注解处理器需要继承 javax.annotation.processing.AbstractProcessor 抽象类并且覆写处理注解的方法。
@SupportedAnnotationTypes("com.yuk.customannotaion.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("进入注解处理器");
// 获取到注解@BindView
Set<? extends Element> es = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element e : es) {
// 强转成变量元素
VariableElement ve = (VariableElement) e;
// 获取变量元素所在的类
TypeElement enclosingElement = (TypeElement) ve.getEnclosingElement();
// 获取类名和包名
String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getSimpleName().toString();
// 控件ID
int id = ve.getAnnotation(BindView.class).value();
// 控件名称
String fieldName = ve.getSimpleName().toString();
System.out.println(String.format("packageName=%s, className=%s, fieldName=%s, id=%d", packageName, className, fieldName, id));
}
System.out.println("退出注解处理器");
return true;
}
}
2.5 使用 apt 工具解析注解
在注解处理器完成后,需要通过 apt 工具把注解处理器编译并生成处理结果,并在运行时被解析。
2.5.1 编译注解处理器
使用如下命令编译注解处理器:
javac -encoding UTF-8 MyAnnotationProcessor.java
之后会生成 MyAnnotationProcessor.class 文件。
2.5.2 使用 apt 工具解析注解
使用如下命令编译包含 @BindView 注解的类:
javac -processor com.xxx.xxx.MyAnnotationProcessor TestAnnotation.java
在编译时,Apt 会扫描 @BindView 注解并调用 @BindView 注解处理器生成相应的代码,生成的代码就是把 @BindView 注解的字段和控件ID绑定起来的代码。
关注微信公众号:“小虎哥的技术博客”。我们会定期发布关于Java技术的详尽文章,让您能够深入了解该领域的各种技巧和方法,让我们一起成为更优秀的程序员👩💻👨💻!