Java Annotations初探

以前刚开始学Java的时候,就听说了Java Annotations了,是在Java5中引入的,可是一直也没用到,所以也就没怎么研究了,最近在看Webx 3 (what is Webx ?) 的源代码,看到里面用到了这个东东,就准备把这个Annotation研究下。先看一下Java官网上怎么说的。这是个Java Annotation的介绍:http://download.oracle.com/javase/1.5.0/docs/guide/language/annotations.html,全英文,但是原汁原味,硬着头皮看吧,顺便用自己的理解把它汉化。

=====begin========

许多API需要大量的样本(boilerplate)代码,比如,如果我要写一个基于JAX_RPC的web service,那么我不得不提供一个配对的接口(interface)和实现。但是,如果我把这个程序中可以远程访问的方法用annotation来“装饰”起来,那么,这些样本代码就可以通过一个特定的工具来自动生成。

另外,也有一些API需要同它的“附属文件”一块来维护。比如,JavaBeans 需要同时维护一个bean class和它对应的BeanInfo class,而EJB则需要同时维护一个部署描述文件(deployment descriptor)。而如果这些存在于附属文件中信息能够以“annotations”的形式保存在程序中,那么将会更加方便(因为两者在一处维护)和更加的不容易出错。

Java平台(Java  plateform)本身就有许多专用的annotations机制。比如 “transient”修饰符(modifier)就是一个专用的annotation,用来表明被修饰的字段(field)在序列化(serialization)的时候被忽略。而@deprecated javadoc标记则是一个用来表明此方法不应该再被使用的专用annotation。自从Java5开始,Java平台提供了一个通用的annotation工具(也被称为metadata,元数据)来允许你定义和使用你自己的annotation类型。这些工具包括声明(declaring)annotation类型的语法,注解声明(annotating declarations)的语法,读取annotations的API,annotations的类文件的表示形式,以及annotation处理工具。

annotations不会直接影响程序的语义,但是他们确实影响工具和库处理程序的方式,进而影响运行中的程序的语义。annotations可以从源文件 ,class文件,或者运行时的反射中读取。

annotations扩充了javadoc tags。通常,如果标记(markup)是准备用来影响或者产生文档的,那么它应该可能是一个javadoc tag;否则,它应该是一个annotation。

典型的应用开发程序员可能永远不必要自己定义一个annotation类型(type),但是,即使这么做,也不会太难。annotation类型声明(declarations)和普通的借口定义很相似,只不过在interface关键字前面要加一个“@”符号。接口中的声明的每一个方法定义了该annotation类型的一个元素(element)。方法声明中不能有任何的参数(parameter)或者throws 片段,返回的类型被限制在基本类型,String,Class,enums,annotations,和这些类型的数组形式。方法可以有默认的值(default values)。以下是一个annotation类型声明的例子:

/**
* Describes the Request-For-Enhancement(RFE) that led
* to the presence of the annotated API element.
*/
public @interface RequestForEnhancement {
int    id();
String synopsis();
String engineer() default "[unassigned]";
String date();    default "[unimplemented]";
}

一旦一个annotation类型被定义,则就可以用它来注解声明(annotate declaration)了。一个annotation 是一种特殊的修饰符(modifier),可以被使用在其他修饰符(比如public,static,final)可以使用的任何地方。一般来说,惯例是把annotations放在其他修饰符的前面。annotation包括一个@,后加annotation类型和被小括号括起来的一系列"元素-值"对(list of element-value pairs). 这些值必须是编译时的常量(compile-time constants)。这里是带有上面定义的annotation类型对应的annotation的方法的定义( method declaration with an annotation corresponding to the annotation type declared above):

@RequestForEnhancement(
id       = 2868724,
synopsis = "Enable time-travel",
engineer = "Mr. Peabody",
date     = "4/1/3007"
)
public static void travelThroughTime(Date destination) { ... }

一个没有元素的annotation类型被称为“标记annotation类型”(marker annotation type),比如:

public @interface Preliminary { }

对于这种“标记annotation类型”,在使用时,可以省略掉后边的圆括号。比如对于上面定义的annotation类型:

@Preliminary public class TimeTravel { ... }

对于只包含一个元素的annotation类型,这个元素的名字应该被命名为value,如下所示:

public @interface Copyright {
String value();
}

在这种情况下(单元素,元素名字为value),在使用时可以省略掉元素名字(即value)和“=”,比如:

@Copyright("2002 Yoyodyne Propulsion Systems")
public class OscillationOverthruster { ... }

为了回顾一下我们上面学的,我们将会构建一个简单的基于annotation的测试框架。首先我们需要一个“标记annotation类型”(marker annotation type,还记得吗,就是没有元素的annotation类型)来表明一个方法是一个测试方法,这个方法应该被测试工具执行:

import java.lang.annotation.*;

/**
* Indicates that the annotated method is a test method.
* This annotation should be used only on parameterless static methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test { }

注意,这个annotation类型声明本身也被注解(annotated)了,像这种类型的annotations叫做元注解(meta-annotations).第一个注解@Retention(RetentionPolicy.RUNTIME)表明这个注解类型是被虚拟机(VM)持有的,以便它们可以在运行时被反射的读取(read reflectively at run-time)。第二个注解@Target(ElementType.METHOD)则表明这个annotation类型只能被用来注解方法声明。

下面是一个样例程序,一些方法(method)被上面提到的接口annotated了:

public class Foo {
@Test public static void m1() { }
public static void m2() { }
@Test public static void m3() {
throw new RuntimeException("Boom");
}
public static void m4() { }
@Test public static void m5() { }
public static void m6() { }
@Test public static void m7() {
throw new RuntimeException("Crash");
}
public static void m8() { }
}

下面是测试工具:

import java.lang.reflect.*;

public class RunTests {
public static void main(String[] args) throws Exception {
int passed = 0, failed = 0;
for (Method m : Class.forName(args[0]).getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
try {
m.invoke(null);
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %n%s %n", m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
}
}

这个工具将一个class name作为命令行参数(command line argument),并且迭代这个class的所有方法,尝试调用带有@Test注解的方法。带下划线的部分表示通过反射方法来判定一个方法是否带有@Test注解。如果一个测试方法调用抛出一个异常,则被认定测试失败,并且打印失败report。最后,打印出测试成功和失败的数目信息。下面是当你利用Foo class运行测试工具时的运行结果:

$ java RunTests Foo
Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
Passed: 2, Failed 2

While this testing tool is clearly a toy, it demonstrates the power of annotations and could easily be extended to overcome its limitations.(尽管这个测试工具看上去像个玩具,但它却显示了annotations的power,并且可以很容易的扩展)。

=====end=======

额, 算了一下,边看边翻译,最后加在电脑上运行程序,一共花了2个小时(23:00-1:00)。虽然花的时间很多,但是好在这边讲的的弄明白了。

睡觉了,明天还得上班呢。。。

posted @ 2011-04-14 01:02  gshine  阅读(1897)  评论(0编辑  收藏  举报