注解
注解,其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,可以在不改变原有逻辑的情况下,在源文件中嵌入补充一些信息。
Annotation 提供了一种为程序元素设置元数据的方法,Annontation 就像修饰符一样可以修饰包、类、构造器、成员变量的声明,这些信息被存储在 Annotation 的 "name=value" 对中。
Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来取得注解里的元数据。
基本 Annotation
- @Override
- @Deprecated
- @SuppressWarnings
- @FunctionalInterface:用来指定某个接口必须是函数式接口
Meta Annotation
java.lang.annotation 包下提供了 6 个元 Annotation ,其中 5 个用于修饰其他的 Annotation 定义。 其中 @Repeatable 专门用于定义 Java8 新增的重复注解。
@Retention
用于指定被修饰的 Annotation 可以保留多长时间。
@Retention 包含一个 RetentionPolicy 类型的 value 成员变量,使用 @Retention 时必须为该 value 变量指定值。
value 成员变量的只能是如下三个:
RetentionPolicy.CLASS:编译器将 Annotation 记录在 class 文件中。当运行程序时,JVM 不可获取 Annotation 信息。这是默认值
RetentionPolicy.RUNTIME:编译器将 Annotation 记录在 class 文件中。程序可以通过反射获取该 Annotation 信息
RetentionPolicy.SOURCE:Annotation 只保留在源代码中,编译器直接丢弃这种 Annotation
// 定义下面的 Testable Annotation 保留到运行时
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Testable()
@Target
用于指定被修饰的 Annotation 能用于修饰哪些程序单元,比如构造器、成员变量、局部变量、方法等。
@Target 也包含一个名为 value 的成员变量。具体的取值请查看 API 文档。
// 下面代码指定 @ActionListenFor Annotation 只能修饰方法
@Target(value = ElementType.Method)
public @interface ActionListenFor{}
@Documented
@Documented 用于指定被该 Meta Annotation 修饰的 Annotation 将被 javadoc 工具提取成文档。
@Inherited
@Inherited 指定被它修饰的 Annotation 将具有继承性。如果某个类使用了 @XXX Annotation ,(且定义该 Annotation 时使用了 @Inherited 修饰)修饰,那么这个类的子类将自动被 @XXX 修饰。
自定义 Annotation
关键字
使用 @interface 关键字。
// 定义一个简单的 Annotation 类型
public @interface Test {}
默认情况下,Annotation 可以用于修饰任何程序元素,包括类、接口、方法等。
public class MyClass {
// 使用 @Test Annotation 修饰方法
@Test
public void info() {}
}
成员变量
Annotation 可以携带成员变量。
成员变量在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。
public @interface MyTag {
// 定义两个带成员变量的 Annotation
String name();
int age();
}
public class Test {
@MyTag(name="xx", age=6)
public void info() {}
}
定义 Annotation 的成员变量时为其指定默认值,使用 default 关键字。
这样在使用该 Annotation 时可以不为这些成员变量指定值,而是直接使用默认值。
public @interface MyTag {
// 定义两个带成员变量的 Annotation
String name() default "Tianny";
int age() default 32;
}
提取 Annotation 信息
原理
Java 提供 Annotation 接口来表示程序元素前的注解,该接口是所有注解的父接口。
java.lang.reflect 包下的 AnnotatedElement 接口,表示可以接受注解的程序元素。
AnnotatedElement 接口主要有如下几个实现类:
- Class: 类定义
- Constructor:构造器定义
- Field:成员变量定义
- Method:方法定义
- Package:包定义
AnnotatedElement 接口是所有程序元素的父接口,所以通过反射获取某个类的 AnnotatedElement 对象(如 Class、Method、Constructor 等)之后,就可以调用该对象的指定方法获取 Annotation 信息。
提取
获取某个注解里的元数据,可以将注解强制转换成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据。
// 获取 tt 对象的 info 方法所包含的所有注解
Annotation[] annotation = tt.getClass().getMethod("info").getAnnotations();
// 遍历每个注解对象
for (Annotation tag : annotation) {
// 如果 tag 注解是 MyTag 类型
if (tag instanceof MyTag) {
System.out.prinln("Tag is " + tag);
// 将 tag 强制类型转换为 MyTag
System.out.prinln("tag.name(): " + ((MyTag)tag).name());
System.out.prinln("tag.age(): " + ((MyTag)tag).age());
}
}
使用 Annotation
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 定义一个注解,不包含任何成员变量,即不可传入元数据
public @interface Testable {
}
public class MyTest {
// 使用 @Testable 注解指定该方法是可测试的
@Testable
public static void m1() {}
@Testable
public static void m2() {}
@Testable
public static void m3() {
throw new RuntimeException("异常");
}
public static void m4() {}
}
import java.lang.reflect.*;
public class ProcessorTest {
public static void process(String clazz) throws ClassNotFoundException {
int passwd = 0;
int failed = 0;
// 遍历 clazz 类里对应的所有方法
for (Method m : Class.forName(clazz).getMethods()) {
// 如果该方法使用了 @Testable 修饰
if (m.isAnnotationPresent(Testable.class)) {
try {
// 调用 m 方法
m.invoke(null);
// 测试成功,passwd 计数 + 1
passwd++;
} catch (Exception e) {
System.out.println("方法" + m + "运行失败,异常: " + e.getCause());
failed++;
}
}
}
System.out.println("共运行了: " + (passwd + failed) + "个方法,其中: \n" + "失败了: " + failed + "个, \n" + "成功了: " + passwd + "个");
}
}
public class RunTests {
public static void main(String[] args) throws Exception {
ProcessorTest.process("MyTest");
}
}
Java8 新增的重复注解
在同一个元素类型前,多次使用多个相同类型的注解,就是重复注解。
Java8 允许使用多个相同类型的 Annotation 来修饰同一个元素。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authority {
String role();
}
@Repeatable(Authorities.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
// 重复注解
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
欢迎关注我的公众号