Sun公司决定在Java的JDK 5版本中包括的另一个特性是元数据功能。这使得人们可以使用工具能够分析的额外信息来标记类,同时也可以自动向类应用特定代码块。元数据功能在java.lang. annotation包中被引入。注释是指Java中一个tag与构造(例如类,在注释术语中被称为目标)之间的关联。能够被注释的构造的类型在java.lang.annotation.ElementType枚举中列出,同时在下面的表1-3中列出。甚至注释也可以被注释。TYPE覆盖了类、接口和enum声明。
表1-3
ElementType常量 | ElementType常量 |
ANNOTATION_TYPE | METHOD |
CONSTRUCTOR | PACKAGE |
FIELD | PARAMETER |
LOCAL_VARIABLE | TYPE |
另一个引入的概念是注释的生命周期,被称为保持(retention)。某些注释也许只是在Java源代码级别上才有用,例如javadoc工具的注释。其他的注释也许在程序正在执行时才需要。RetentionPolicy枚举列出了一个注释的3个type生命周期。SOURCE策略表示注释应该由编译器丢弃,也就是说,该注释只在源代码级别上有用。CLASS策略表示注释应该在类文件中出现,但是在运行时可能被丢弃。RUNTIME策略则表示注释在整个程序执行期间都应该有效,并且这些注释可以通过反射来查看。
在此包中定义了几个注释类型。表1-4中列出了这些类型。这些注释中的每一个都是从Annotation接口继承而来,该接口定义了equals方法和toString方法。
表1-4
注释类名称 | 描述 |
Target | 指定一个注释类型可以应用的程序元素。每个程序元素只能出现一次 |
Documented | 指定注释应该通过javadoc或者其他存档工具存档。这只能应用于注释 |
Inherited | 从超类继承注释,而不是接口。作用于此注释的策略是RUNTIME,它只能应用于注释 |
Retention | 表示作用于程序元素上的注释应该多长时间有效。请参见前面讨论的RetentionPolicy。作用于此注释的策略是RUNTIME,它只能应用于注释 |
Deprecated | 标记一个不赞成使用的程序元素,告诉开发人员他们不应该再使用该元素。保持策略是SOURCE |
Overrides | 表示一个方法将要覆盖一个父类中的方法。如果确实不存在覆盖,那么编译器将会生成一个错误消息。这只能应用于方法 |
JDK 5引入了两个有用的源代码级别注释,即@deprecated和@overrides。@deprecated注释用来标记一个不赞成使用的方法,也就是说,客户端程序员不应该使用它。当在一个类方法中遇到程序员使用这种注释时,编译器将会发出一个警告。另一个注释@overrides,用来标记一个覆盖父类中方法的方法。编译器将会确认标记为@overrides的方法真正是覆盖了父类中的一个方法。如果子类中的方法没有覆盖父类中的方法,那么编译器将会发送一个错误,警告程序员方法签名不匹配父类中的方法。
开发一个自定义注释并不困难。下面创建一个CodeTag注释,它可以存储基本的作者信息和修改日期信息,同时也存储任意应用到该段代码的bug修复。注释将被限制到类和方法上:
import java.lang.annotation.*;
@Retention(RetentionPolicy. SOURCE)
@Target({ElementType. TYPE, ElementType.METHOD})
public @interface CodeTag {
String authorName();
String lastModificationDate();
String bugFixes() default "";
}
Retention被设置为SOURCE,这意味着此注释在编译时和运行时是无效的。doclet API用来访问源代码级别上的注释。Target(对于类/接口/枚举)被设置为TYPE,对于方法则被设置为METHOD。如果CodeTag注释被应用到任意其他的源代码元素上,那么就会生成一个编译器错误。前面的两个注释元素是authorName和lastModificationDate,两者都是必要的。bugFixes元素如果没有指定,那么默认是空字符串。下面是一个使用CodeTag注释的示例类:
import java.lang.annotation.*;
@CodeTag(authorName="Dilbert",
lastModificationDate="May 7, 2006")
public class ServerCommandProcessor {
@CodeTag(authorName="Dilbert",
lastModificationDate="May 10, 2006",
bugFixes="BUG0170")
public void setParams(String serverName)
{
//…
}
public void executeCommand(String command, Object... params)
{
//…
}
}
注意注释是如何用来标记谁对源代码进行了修改以及是何时修改的。由于bug的修复从而使得方法在类修改之后一天被修改。自定义的注释可以用来追踪关于源代码修改的信息。为了查看或者处理这些源代码注释,就必须使用doclet API。
doclet API(也叫做Javadoc API)已经扩展到可以支持源代码中的注释处理。要使用doclet API,就必须在类路径中包括tools.jar文件(对于版本5或更高的版本,位于默认JDK安装的库文件目录中)。通过编写一个扩展了com.sun.javadoc.Doclet的Java类就可以使用doclet API。必须实现start方法,因为这是Javadoc调用doclet以执行自定义处理的方法。下面这个简单的doclet打印一个Java源文件中的所有类和方法:
import com.sun.javadoc.*;
public class ListClasses extends Doclet {
public static boolean start(RootDoc root) {
ClassDoc[] classes = root.classes();
for (ClassDoc cd : classes) {
System.out.println("Class [" + cd + "] has the following methods");
for(MemberDoc md : cd.methods()) {
System.out.println(" "+ md);
}
}
return true;
}
}
start方法将RootDoc作为一个参数,该参数通过javadoc工具被自动传入。从RootDoc开始可以访问源代码中的所有元素,以及关于命令行(例如增加的包和类)的信息。
针对注释添加到doclet API中的接口是AnnotationDesc、AnnotationDesc.Element ValuePair、AnnotationTypeDoc、AnnotationTypeElementDoc和AnnotationValue。
Java源代码中可以有注释的任意元素都具有一个annotations()方法,该方法与doclet API同源代码元素的对应部分相关联。这些元素是AnnotationTypeDoc、AnnotationTypeElementDoc、ClassDoc、ConstructorDoc、ExecutableMemberDoc、FieldDoc、MethodDoc以及MemberDoc。Annotations()方法返回一个AnnotationDesc数组。
1.AnnotationDesc
此类代表一个注释,它包括一个注释类型(AnnotationTypeDoc)和一个同它们的值相配对的注释类型元素数组。AnnotationDesc定义了表1-5中的方法。
表1-5
方法 | 描述 |
AnnotationTypeDoc annotationType() | 返回这个注释的类型 |
AnnotationDesc.ElementValuePair[] elementValues() | 返回一个注释元素的数组以及它们的值。只返回被显式列出的元素。没有被显式列出的元素(已经假设了它们的默认值),将不会被返回,因为此方法只处理被列出的元素。如果没有元素,那么将返回一个空数组 |
2.AnnotationDesc.ElementValuePair
它代表了一个注释类型的元素及其值之间的关联。它定义了表1-6中的方法。
表1-6
方法 | 描述 |
AnnotationTypeElementDoc element() | 返回注释类型元素 |
AnnotationValue value() | 返回注释类型元素的值 |
3.AnnotationTypeDoc
此接口代表了源代码中的一个注释,就如同ClassDoc代表一个Class一样。它只定义了一个方法,见表1-7。
表1-7
方法 | 描述 |
AnnotationTypeElementDoc[] elements() | 返回此注释类型的元素的数组 |
4.AnnotationTypeElementDoc
此接口代表了一个注释类型的元素,它定了一个方法,见表1-8。
表1-8
方法 | 描述 |
AnnotationValue defaultValue() | 返回与此注释类型相关联的默认值;如果没有默认值,则返回null |
5.AnnotationValue
此接口代表了一个注释类型元素的值,它定义的方法见表1-9。
表1-9
方法 | 描述 |
String toString() | 返回该值的一个字符串表示形式 |
Object value() | 返回值。此值所表示的对象可以是下面的任意类型: |
下面是一个使用doclet API所提供的注释支持的示例。此doclet显示它在一个源文件中发现的所有注释及其值:
import com.sun.javadoc.*;
import java.lang.annotation.*;
public class AnnotationViewer {
public static boolean start(RootDoc root)
{
ClassDoc[] classes = root.classes();
for (ClassDoc cls : classes) {
showAnnotations(cls);
}
return(true);
}
static void showAnnotations(ClassDoc cls)
{
System.out.println("Annotations for class [" + cls + "]");
process(cls.annotations());
System.out.println();
for(MethodDoc m : cls.methods()) {
System.out.println("Annotations for method [" + m + "]");
process(m.annotations());
System.out.println();
}
}
static void process(AnnotationDesc[] anns)
{
for (AnnotationDesc ad :anns) {
AnnotationDesc.ElementValuePair evp[] = ad.elementValues();
for(AnnotationDesc.ElementValuePair e : evp) {
System.out.println(" NAME: " + e.element() +
", VALUE=" + e.value()) ;
}
}
}
}
Start方法迭代了在源文件中发现的所有类(和接口)。因为在源代码元素上的所有注释都是与AnnotationDesc接口相关联的,所以可以编写一个方法来处理注释,而不用考虑该注释关联的是哪个代码元素。showAnnotations方法打印出同当前类相关联的所有注释,然后处理该类中的所有方法。doclet API使得处理这些源代码元素很容易。为了执行doclet,可以按照如下方式向命令行的程序传递doclet名称和类名称:
javadoc -doclet AnnotationViewer ServerCommandProcessor.java
doclet在屏幕上显示如下内容:
Loading source file ServerCommandProcessor.java...
Constructing Javadoc information...
Annotations for class [ServerCommandProcessor]
NAME: CodeTag.authorName(), VALUE="Dilbert"
NAME: CodeTag.lastModificationDate(), VALUE="May 7, 2006"
Annotations for method [ServerCommandProcessor.setParams(java fang. String)]
NAME: CodeTag.authorName(), VALUE="Dilbert"
NAME: CodeTag.lastModificationDate(), VALUE="May 10, 2006"
NAME: CodeTag.bugFixes(), VALUE="BUG0170"
Annotations for method [ServerCommandProcessor.executeCommand (java.lang. Strinq,
java.lang.Object[])]
要在运行时访问注释,就必须使用反射API。通过AnnotatedElement接口已经内置了这种支持,该接口是通过反射类AccessibleObject、Class、Constructor、Field、Method和Package来实现的。所有这些元素都可以有注释。