在运行时使用反射获取注解
尽管设计注解的目的主要是用于其他的开发和部署工具,但是如果为注解指定RUNTIME保留策略,那么任何程序在运行时都可以使用反射来查询注解。反射是能够在运行时获取类相关信息的特性。反射API位于java.lang.reflect包中。使用反射的方式有很多,在此不可能解释所有这些方式。但是,我们将分析应用了注解的几个例子。使用反射的第一步是获取Class对象,表示希望获取其中注解的类。Class是Java的内置类,是在java.lang包中定义的。在本书第Ⅱ部分将对这个包进行详细介绍。可以使用多种方式来获取Class对象。其中最简单的方式是调用getClass()方法,该方法是由Object类定义的,它的一般形式如下所示:
final Class<?> getClass( )
该方法返回用来表示调用对象的Class对象。
注意:
注意上面显示的getClass()方法声明中跟在Class后面的<?>,这与Java中的泛型特性有关。在本章讨论的getClass()方法以及其他几个与反射有关的方法,需要使用泛型。泛型将在第14章介绍。但是,理解反射的基本原则不需要先理解泛型。
获得Class对象后,可以使用其他方法获取与类声明中各个条目相关的信息,包括注解。如果希望获取与类声明中特定条目关联的注解,那么首先必须获取表示该特定条目的对象。
例如,Class提供了getMethod()、getField()以及getConstructor()方法(还有其他方法),这些方法分别获取与方法、域变量以及构造函数相关的信息,这些方法返回Method、Field 以及Constructor类型的对象。
为了理解这个过程,分析一个获取与方法关联的注解的例子。为此,首先获取表示类的Class对象,然后调用Class对象的getMethod()方法并指定方法的名称。getMethod()方法的一般形式如下:
Method getMethod(String methName, Class<?> ... paramTypes)
方法的名称被传递到methName中。如果方法有参数,那么必须通过paramTypes指定表示这些参数类型的Class对象。注意paramTypes是可变长度参数,这意味着可以指定需要的任意多个参数,包括指定0个参数。getMethod()方法返回表示方法的Method对象。如果没有找到方法,就抛出NoSuchMethodException异常。
对Class、Method、Field以及Constructor对象调用getAnnotation()方法,可以获得与对象关联的特定信息。该方法的一般形式如下:
<A extends Annotation> getAnnotation(Class<A> annoType)
其中,annoType是表示您感兴趣注解的Class对象。该方法返回对注解的一个引用,使用这个引用可以获取与注解成员关联的值。如果没有找到注解,该方法会返回null。如果注解的保留策略不是RUNTIME,就会出现这种情况。
下面的程序总结了在前面介绍的所有内容,并使用反射显示与某个方法关联的注解:
import java.lang.annotation.*;
import java.lang.reflect.*;
// An annotation type declaration.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
class Meta {
// Annotate a method.
@MyAnno(str = "Annotation Example", val = 100)
public static void myMeth() {
Meta ob = new Meta();
// Obtain the annotation for this method
// and display the values of the members.
try {
// First, get a Class object that represents
// this class.
Class<?> c = ob.getClass();
// Now, get a Method object that represents
// this method.
Method m = c.getMethod("myMeth");
// Next, get the annotation for this class.
MyAnno anno = m.getAnnotation(MyAnno.class);
// Finally, display the values.
System.out.println(anno.str() + " " + anno.val());
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
该程序的输出如下所示:
Annotation Example 100
这个程序使用前面介绍的反射,获取并显示与Meta类中myMeth()方法关联的MyAnno注解中str 和val 的值。有两点需要特别注意。第一点,注意下面这行代码中的表达MyAnno.class:
MyAnno anno = m.getAnnotation(MyAnno.class);
对这个表达式求值的结果是表示MyAnno类型的Class对象,即注解。这种结构被称为“类字面值”。无论何时,当需要已知类的Class对象时,就可以使用这类表达式。例如,可以使用下面这条语句获取Meta的Class对象:
Class<?> c = Meta.class;
当然,只有当事先知道对象的类名时才能使用这种方式,但我们并不总是知道对象的类名。通常,可以获取类、接口、基本类型以及数组的类字面值(记住,<?>语法与Java的泛型特性有关,泛型将在第14章介绍)。
需要注意的第二点是,当通过下面这行代码进行输出时,如何获取与str和val关联的数值:
System.out.println(anno.str() + " " + anno.val());
注意这里使用方法调用语法来调用它们。当需要注解成员的值时,可以使用相同的方式。
1. 第二个反射示例
在前面的例子中,myMeth()方法没有参数。因此,当调用getMethod()方法时,只传递名称myMeth。但是,为了获取带有参数的方法,必须指定表示参数类型的类对象作为getMethod()方法的参数。例如,下面的程序与前面的程序稍微有些区别:
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
class Meta {
// myMeth now has two arguments.
@MyAnno(str = "Two Parameters", val = 19)
public static void myMeth(String str, int i)
{
Meta ob = new Meta();
try {
Class<?> c = ob.getClass();
// Here, the parameter types are specified.
Method m = c.getMethod("myMeth", String.class, int.class);
MyAnno anno = m.getAnnotation(MyAnno.class);
System.out.println(anno.str() + " " + anno.val());
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth("test", 10);
}
}
该版本的输出如下所示:
Two Parameters 19
在这个版本中,myMeth()方法带有一个String参数和一个int 参数。为了获取关于这个方法的信息,必须以如下方式调用getMethod()方法:
Method m = c.getMethod("myMeth", String.class, int.class);
在此,作为附加参数传递表示String和int 类型的Class对象。
2. 获取所有注解
可以获取与某个条目关联的具有RUNTIME保留策略的所有注解,具体方法是为该条目调用getAnnotations()方法。该方法的一般形式如下:
Annotation[ ] getAnnotations( )
上述方法返回一个注解数组。可以针对Class、Method、Constructor以及Field类型的对象调用getAnnotations()方法。
下面是另外一个使用反射的例子,该例显示了如何获取与类和方法关联的所有注解。该例声明了两个注解。然后使用这两个注解来注解类和方法。
// Show all annotations for a class and a method.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
@Retention(RetentionPolicy.RUNTIME)
@interface What {
String description();
}
@What(description = "An annotation test class")
@MyAnno(str = "Meta2", val = 99)
class Meta2 {
@What(description = "An annotation test method")
@MyAnno(str = "Testing", val = 100)
public static void myMeth() {
Meta2 ob = new Meta2();
try {
Annotation annos[] = ob.getClass().getAnnotations();
// Display all annotations for Meta2.
System.out.println("All annotations for Meta2:");
for(Annotation a : annos)
System.out.println(a);
System.out.println();
// Display all annotations for myMeth.
Method m = ob.getClass( ).getMethod("myMeth");
annos = m.getAnnotations();
System.out.println("All annotations for myMeth:");
for(Annotation a : annos)
System.out.println(a);
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
输出如下所示:
All annotations for Meta2:
@What(description=An annotation test class)
@MyAnno(str=Meta2, val=99)
All annotations for myMeth:
@What(description=An annotation test method)
@MyAnno(str=Testing, val=100)
该程序使用getAnnotations()方法来获取与类Meta2和方法myMeth()相关联的所有注解,并将它们保存到数组中。正如前面所解释的,getAnnotations()方法返回Annotation对象的数组。回忆一下,Annotation是所有注解接口的超接口,并且它重载了Object类中的toString()方法。因此,当输出对Annotation的引用时,会调用toString()方法来生成描述注解的字符串,如前面的输出所示。
页:
[1]